glastree_on_gitea
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ActivityLogController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = ActivityLog::with('user');
|
||||
|
||||
if ($request->filled('user_id')) {
|
||||
$query->where('user_id', $request->user_id);
|
||||
}
|
||||
|
||||
if ($request->filled('module')) {
|
||||
$query->where('module', $request->module);
|
||||
}
|
||||
|
||||
if ($request->filled('action')) {
|
||||
$query->where('action', $request->action);
|
||||
}
|
||||
|
||||
if ($request->filled('from')) {
|
||||
$query->whereDate('created_at', '>=', $request->from);
|
||||
}
|
||||
|
||||
if ($request->filled('to')) {
|
||||
$query->whereDate('created_at', '<=', $request->to);
|
||||
}
|
||||
|
||||
$logs = $query->orderByDesc('created_at')->paginate(50);
|
||||
$users = User::orderBy('name')->get();
|
||||
$modules = ActivityLog::select('module')->distinct()->pluck('module');
|
||||
|
||||
return view('admin.activity-logs.index', compact('logs', 'users', 'modules'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EmailSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class EmailSettingsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$settings = EmailSetting::first() ?? new EmailSetting();
|
||||
return view('admin.email-settings.index', compact('settings'));
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'imap_host' => 'required|string|max:255',
|
||||
'imap_port' => 'required|integer|min:1|max:65535',
|
||||
'imap_encryption' => 'nullable|in:ssl,tls,none',
|
||||
'imap_username' => 'required|string|max:255',
|
||||
'imap_password' => 'nullable|string',
|
||||
'smtp_host' => 'nullable|string|max:255',
|
||||
'smtp_port' => 'nullable|integer|min:1|max:65535',
|
||||
'smtp_encryption' => 'nullable|in:ssl,tls,none',
|
||||
'email_address' => 'required|email',
|
||||
'email_name' => 'nullable|string|max:255',
|
||||
'reply_to' => 'nullable|email',
|
||||
'sync_interval_minutes' => 'nullable|integer|min:1|max:60',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
if (!empty($validated['imap_password'])) {
|
||||
$validated['imap_password'] = Crypt::encryptString($validated['imap_password']);
|
||||
} else {
|
||||
unset($validated['imap_password']);
|
||||
}
|
||||
if (!empty($validated['smtp_password'])) {
|
||||
$validated['smtp_password'] = Crypt::encryptString($validated['smtp_password']);
|
||||
} else {
|
||||
unset($validated['smtp_password']);
|
||||
}
|
||||
|
||||
$saved = EmailSetting::updateOrCreate(
|
||||
['id' => $request->id ?? 1],
|
||||
$validated
|
||||
);
|
||||
|
||||
// Verifica che i dati siano stati salvati
|
||||
$verify = EmailSetting::find($saved->id);
|
||||
if ($verify &&
|
||||
$verify->imap_host === $validated['imap_host'] &&
|
||||
$verify->email_address === $validated['email_address']) {
|
||||
return back()->with('success', 'Impostazioni email salvate e verificate nel database.');
|
||||
} else {
|
||||
return back()->with('error', 'Errore nel salvataggio: i dati non sono stati salvati correttamente.');
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnection(Request $request)
|
||||
{
|
||||
$settings = EmailSetting::first();
|
||||
|
||||
if (!$settings) {
|
||||
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
||||
}
|
||||
|
||||
// Verifica che i dati siano salvati
|
||||
if (empty($settings->imap_host) || empty($settings->email_address)) {
|
||||
return response()->json(['success' => false, 'message' => 'Prima salva le impostazioni.']);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $settings->testConnection();
|
||||
return response()->json($result);
|
||||
} catch (\Exception $e) {
|
||||
$msg = $e->getMessage();
|
||||
if (strpos($msg, 'Undefined property') !== false) {
|
||||
$msg = 'Impossibile connettersi al server IMAP. Verifica host, porta, encryption e credenziali.';
|
||||
}
|
||||
return response()->json(['success' => false, 'message' => 'Errore: ' . $msg]);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSmtp(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
$settings = EmailSetting::first();
|
||||
|
||||
if (!$settings) {
|
||||
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
||||
}
|
||||
|
||||
if (empty($settings->smtp_host)) {
|
||||
$settings->smtp_host = 'smtp.gmail.com';
|
||||
$settings->smtp_port = 587;
|
||||
$settings->smtp_encryption = 'tls';
|
||||
}
|
||||
|
||||
try {
|
||||
$smtpUsername = $settings->smtp_username;
|
||||
$smtpPassword = $settings->smtp_password;
|
||||
|
||||
if (empty($smtpUsername)) {
|
||||
$smtpUsername = $settings->email_address;
|
||||
}
|
||||
if (empty($smtpPassword)) {
|
||||
$smtpPassword = $settings->getDecryptedPassword();
|
||||
} else {
|
||||
$smtpPassword = Crypt::decryptString($smtpPassword);
|
||||
}
|
||||
|
||||
$encryption = $settings->smtp_encryption ?? 'tls';
|
||||
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||
|
||||
$dsn = sprintf(
|
||||
'%s://%s:%s@%s:%d?encryption=%s',
|
||||
$scheme,
|
||||
rawurlencode($smtpUsername),
|
||||
rawurlencode($smtpPassword),
|
||||
$settings->smtp_host,
|
||||
$settings->smtp_port ?? 587,
|
||||
$encryption
|
||||
);
|
||||
|
||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||
|
||||
$fromName = $settings->email_name ?? '';
|
||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
||||
$fromName = 'Glastree';
|
||||
} else {
|
||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
||||
$fromName = trim($fromName);
|
||||
if (empty($fromName) || strlen($fromName) > 50) {
|
||||
$fromName = 'Glastree';
|
||||
}
|
||||
}
|
||||
|
||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
||||
|
||||
$email = (new \Symfony\Component\Mime\Email())
|
||||
->from($fromAddress)
|
||||
->to($request->email)
|
||||
->subject('Test Configurazione SMTP - Glastree')
|
||||
->text('Test email da Glastree - Configurazione SMTP funziona!');
|
||||
|
||||
$mailer->send($email);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('Test SMTP ok', ['to' => $request->email, 'from' => $settings->email_address]);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Email di test inviata a ' . $request->email]);
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error('Test SMTP failed', ['error' => $e->getMessage()]);
|
||||
return response()->json(['success' => false, 'message' => 'Errore invio: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncNow()
|
||||
{
|
||||
$settings = EmailSetting::getActive();
|
||||
|
||||
if (!$settings) {
|
||||
return back()->with('error', 'Nessuna configurazione email attiva.');
|
||||
}
|
||||
|
||||
try {
|
||||
app(\App\Http\Controllers\EmailController::class)->syncEmails();
|
||||
$settings->update(['last_sync_at' => now()]);
|
||||
return back()->with('success', 'Sincronizzazione completata.');
|
||||
} catch (\Exception $e) {
|
||||
return back()->with('error', 'Errore sync: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\RolePreset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RuoloController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$ruoli = RolePreset::withCount('users')->get();
|
||||
return view('admin.ruoli.index', compact('ruoli'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.ruoli.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:50|unique:role_presets,name',
|
||||
'description' => 'nullable|string|max:255',
|
||||
'permissions' => 'required|array',
|
||||
'permissions.*' => 'in:0,1,2',
|
||||
]);
|
||||
|
||||
$ruolo = RolePreset::create([
|
||||
'name' => $validated['name'],
|
||||
'description' => $validated['description'] ?? null,
|
||||
'permissions' => $validated['permissions'],
|
||||
]);
|
||||
|
||||
ActivityLog::log('create', 'ruoli', 'Creato ruolo: ' . $ruolo->name, [
|
||||
'role_id' => $ruolo->id,
|
||||
'permissions' => $validated['permissions'],
|
||||
]);
|
||||
|
||||
return redirect('/admin/ruoli')->with('success', 'Ruolo creato.');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$ruolo = RolePreset::findOrFail($id);
|
||||
$usersCount = $ruolo->users()->count();
|
||||
return view('admin.ruoli.edit', compact('ruolo', 'usersCount'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$ruolo = RolePreset::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'description' => 'nullable|string|max:255',
|
||||
'permissions' => 'required|array',
|
||||
'permissions.*' => 'in:0,1,2',
|
||||
]);
|
||||
|
||||
$old = $ruolo->permissions;
|
||||
|
||||
$ruolo->description = $validated['description'];
|
||||
$ruolo->permissions = $validated['permissions'];
|
||||
$ruolo->save();
|
||||
|
||||
ActivityLog::log('update', 'ruoli', 'Modificato ruolo: ' . $ruolo->name, [
|
||||
'role_id' => $ruolo->id,
|
||||
'old_permissions' => $old,
|
||||
'new_permissions' => $validated['permissions'],
|
||||
]);
|
||||
|
||||
if ($request->input('update_users') === '1') {
|
||||
foreach ($ruolo->users as $user) {
|
||||
$user->permissions = $ruolo->permissions;
|
||||
$user->save();
|
||||
}
|
||||
return redirect('/admin/ruoli')->with('success', 'Ruolo aggiornato e permessi propagati a ' . $ruolo->users()->count() . ' utenti.');
|
||||
}
|
||||
|
||||
return redirect('/admin/ruoli')->with('success', 'Ruolo aggiornato.');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$ruolo = RolePreset::findOrFail($id);
|
||||
|
||||
if ($ruolo->users()->count() > 0) {
|
||||
return redirect('/admin/ruoli')->with('error', 'Impossibile eliminare: ci sono ' . $ruolo->users()->count() . ' utenti con questo ruolo.');
|
||||
}
|
||||
|
||||
$name = $ruolo->name;
|
||||
|
||||
ActivityLog::log('delete', 'ruoli', 'Eliminato ruolo: ' . $name, [
|
||||
'role_id' => $id,
|
||||
'permissions' => $ruolo->permissions,
|
||||
]);
|
||||
|
||||
$ruolo->delete();
|
||||
|
||||
return redirect('/admin/ruoli')->with('success', 'Ruolo eliminato.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\RolePreset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class UtenteController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$query->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('email', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
$utenti = $query->orderBy('name')->paginate(20);
|
||||
$rolePresets = RolePreset::all();
|
||||
|
||||
return view('admin.utenti.index', compact('utenti', 'rolePresets'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$rolePresets = RolePreset::all();
|
||||
return view('admin.utenti.create', compact('rolePresets'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|min:8|confirmed',
|
||||
'role_preset_id' => 'nullable|exists:role_presets,id',
|
||||
'is_admin' => 'boolean',
|
||||
'permissions' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$user = new User();
|
||||
$user->name = $validated['name'];
|
||||
$user->email = $validated['email'];
|
||||
$user->password = Hash::make($validated['password']);
|
||||
$user->is_admin = $validated['is_admin'] ?? false;
|
||||
$user->status = User::STATUS_ACTIVE;
|
||||
|
||||
if (!empty($validated['role_preset_id'])) {
|
||||
$preset = RolePreset::find($validated['role_preset_id']);
|
||||
$user->applyRolePreset($preset);
|
||||
} elseif (!empty($validated['permissions'])) {
|
||||
$user->setPermissions($validated['permissions']);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
ActivityLog::log('create', 'utenti', 'Creato utente: ' . $user->name, [
|
||||
'user_id' => $user->id,
|
||||
'email' => $user->email,
|
||||
]);
|
||||
|
||||
return redirect('/admin/utenti')->with('success', 'Utente creato.');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
$rolePresets = RolePreset::all();
|
||||
return view('admin.utenti.edit', compact('user', 'rolePresets'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email,' . $id,
|
||||
'password' => 'nullable|min:8|confirmed',
|
||||
'role_preset_id' => 'nullable|exists:role_presets,id',
|
||||
'is_admin' => 'boolean',
|
||||
'status' => 'required|in:active,suspended,pending',
|
||||
'permissions' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$old = $user->toArray();
|
||||
|
||||
$user->name = $validated['name'];
|
||||
$user->email = $validated['email'];
|
||||
$user->is_admin = $validated['is_admin'] ?? false;
|
||||
$user->status = $validated['status'];
|
||||
|
||||
if (!empty($validated['password'])) {
|
||||
$user->password = Hash::make($validated['password']);
|
||||
}
|
||||
|
||||
if (!empty($validated['role_preset_id'])) {
|
||||
$preset = RolePreset::find($validated['role_preset_id']);
|
||||
$user->applyRolePreset($preset);
|
||||
} elseif (isset($validated['permissions'])) {
|
||||
$user->setPermissions($validated['permissions']);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
ActivityLog::log('update', 'utenti', 'Modificato utente: ' . $user->name, [
|
||||
'user_id' => $user->id,
|
||||
'old' => $old,
|
||||
'new' => $user->toArray(),
|
||||
]);
|
||||
|
||||
return redirect('/admin/utenti')->with('success', 'Utente aggiornato.');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
if ($user->id === auth()->id()) {
|
||||
return redirect('/admin/utenti')->with('error', 'Non puoi eliminare te stesso.');
|
||||
}
|
||||
|
||||
if ($user->isSuperAdmin()) {
|
||||
$adminCount = User::where('is_admin', true)->count();
|
||||
if ($adminCount <= 1) {
|
||||
return redirect('/admin/utenti')->with('error', 'Impossibile eliminare l\'ultimo amministratore.');
|
||||
}
|
||||
}
|
||||
|
||||
$userName = $user->name;
|
||||
|
||||
ActivityLog::log('delete', 'utenti', 'Eliminato utente: ' . $userName, [
|
||||
'user_id' => $id,
|
||||
'deleted' => $user->toArray(),
|
||||
]);
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect('/admin/utenti')->with('success', 'Utente eliminato.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function showLoginForm()
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'email' => 'required|email',
|
||||
'password' => 'required',
|
||||
]);
|
||||
|
||||
$user = User::where('email', $credentials['email'])->first();
|
||||
|
||||
if ($user && $user->status === 'suspended') {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['Questo account è sospeso. Contatta un amministratore.'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (Auth::attempt($credentials)) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
ActivityLog::logLogin();
|
||||
|
||||
return redirect()->intended('/dashboard');
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['Le credenziali fornite non sono corrette.'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function showRegisterForm()
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
public function register(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
$isFirstUser = User::count() === 0;
|
||||
|
||||
$defaultPermissions = [
|
||||
'individui' => 1,
|
||||
'gruppi' => 1,
|
||||
'eventi' => 1,
|
||||
'documenti' => 1,
|
||||
'mailing' => 1,
|
||||
'viste' => 1,
|
||||
];
|
||||
|
||||
if ($isFirstUser) {
|
||||
$defaultPermissions = [
|
||||
'individui' => 2,
|
||||
'gruppi' => 2,
|
||||
'eventi' => 2,
|
||||
'documenti' => 2,
|
||||
'mailing' => 2,
|
||||
'viste' => 2,
|
||||
];
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'is_admin' => $isFirstUser,
|
||||
'status' => User::STATUS_ACTIVE,
|
||||
'permissions' => $defaultPermissions,
|
||||
]);
|
||||
|
||||
ActivityLog::log('create', 'utenti', 'Registrato nuovo utente: ' . $user->name, [
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect('/dashboard');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Contatto;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContattoController extends Controller
|
||||
{
|
||||
public function update($contatto)
|
||||
{
|
||||
$this->authorizeWrite('individui');
|
||||
$contatto = Contatto::findOrFail($contatto);
|
||||
$data = request()->validate([
|
||||
'tipo' => 'required|in:telefono,cellulare,email,fax,web,telegram,whatsapp,altro',
|
||||
'valore' => 'required|string|max:255',
|
||||
'etichetta' => 'nullable|string|max:100',
|
||||
'is_primary' => 'nullable',
|
||||
]);
|
||||
|
||||
$contatto->update([
|
||||
'tipo' => $data['tipo'],
|
||||
'valore' => $data['valore'],
|
||||
'etichetta' => $data['etichetta'] ?? null,
|
||||
'is_primary' => isset($data['is_primary']),
|
||||
]);
|
||||
|
||||
$redirect = request('_redirect', route('individui.show', $contatto->individuo_id));
|
||||
return redirect($redirect)->with('success', 'Contatto aggiornato.');
|
||||
}
|
||||
|
||||
public function destroy($contatto)
|
||||
{
|
||||
$this->authorizeDelete('individui');
|
||||
$contatto = Contatto::findOrFail($contatto);
|
||||
$individuo_id = $contatto->individuo_id;
|
||||
$contatto->delete();
|
||||
|
||||
$redirect = request('_redirect', route('individui.show', $individuo_id));
|
||||
return redirect($redirect)->with('success', 'Contatto eliminato.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
protected function authorizeWrite(string $module): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user->canManage($module)) {
|
||||
abort(403, 'Non hai i permessi per modificare ' . $module);
|
||||
}
|
||||
}
|
||||
|
||||
protected function authorizeRead(string $module): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user->canAccess($module)) {
|
||||
abort(403, 'Non hai i permessi per visualizzare ' . $module);
|
||||
}
|
||||
}
|
||||
|
||||
protected function authorizeDelete(string $module): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->isSuperAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user->canDelete($module)) {
|
||||
abort(403, 'Non hai i permessi per eliminare ' . $module);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Documento;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Evento;
|
||||
use App\Models\TipologiaDocumento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DocumentoController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorizeRead('documenti');
|
||||
$documenti = Documento::with(['user', 'target'])
|
||||
->orderByDesc('created_at')
|
||||
->paginate(20);
|
||||
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$eventi = Evento::orderBy('nome_evento')->get();
|
||||
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
||||
$tipologie = TipologiaDocumento::attive();
|
||||
|
||||
return view('documenti.index', compact('documenti', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$eventi = Evento::orderBy('nome_evento')->get();
|
||||
$tipologie = TipologiaDocumento::attive();
|
||||
return view('documenti.create', compact('individui', 'gruppi', 'eventi', 'tipologie'));
|
||||
}
|
||||
public function edit($documento)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$documento = Documento::with('target')->findOrFail($documento);
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$eventi = Evento::orderBy('nome_evento')->get();
|
||||
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
||||
$tipologie = TipologiaDocumento::attive();
|
||||
return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
|
||||
}
|
||||
|
||||
public function options(Request $request)
|
||||
{
|
||||
$tipo = $request->get('tipo');
|
||||
$options = '';
|
||||
|
||||
switch ($tipo) {
|
||||
case 'individuo':
|
||||
$items = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
foreach ($items as $item) {
|
||||
$options .= '<option value="' . $item->id . '">' . e($item->cognome . ' ' . $item->nome) . '</option>';
|
||||
}
|
||||
break;
|
||||
case 'gruppo':
|
||||
$items = Gruppo::orderBy('nome')->get();
|
||||
foreach ($items as $item) {
|
||||
$options .= '<option value="' . $item->id . '">' . e($item->nome) . '</option>';
|
||||
}
|
||||
break;
|
||||
case 'evento':
|
||||
$items = Evento::orderBy('nome_evento')->get();
|
||||
foreach ($items as $item) {
|
||||
$options .= '<option value="' . $item->id . '">' . e($item->nome_evento) . '</option>';
|
||||
}
|
||||
break;
|
||||
case 'mailing':
|
||||
$items = \App\Models\MailingList::orderBy('nome')->get();
|
||||
foreach ($items as $item) {
|
||||
$options .= '<option value="' . $item->id . '">' . e($item->nome) . '</option>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return response($options);
|
||||
}
|
||||
|
||||
public function update(Request $request, $documento)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$documento = Documento::findOrFail($documento);
|
||||
|
||||
$tipologieValidi = TipologiaDocumento::opzioni();
|
||||
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
|
||||
|
||||
$data = $request->validate([
|
||||
'nome_file' => 'required|string|max:255',
|
||||
'tipologia' => 'required|' . $tipologieRule,
|
||||
'visibilita' => 'nullable|string',
|
||||
'visibilita_target_id' => 'nullable|integer',
|
||||
'visibilita_target_type' => 'nullable|string',
|
||||
'note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$data['visibilita'] = $request->contesto_tipo ?: 'pubblico';
|
||||
|
||||
if ($request->contesto_tipo) {
|
||||
$typeMap = [
|
||||
'individuo' => Individuo::class,
|
||||
'gruppo' => Gruppo::class,
|
||||
'evento' => Evento::class,
|
||||
'mailing' => \App\Models\MailingList::class,
|
||||
];
|
||||
$data['visibilita_target_type'] = $typeMap[$request->contesto_tipo] ?? null;
|
||||
} else {
|
||||
$data['visibilita'] = 'pubblico';
|
||||
$data['visibilita_target_id'] = null;
|
||||
$data['visibilita_target_type'] = null;
|
||||
}
|
||||
|
||||
$documento->update($data);
|
||||
|
||||
return redirect('/documenti')->with('success', 'Documento aggiornato.');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$data = $request->validate([
|
||||
'nome_file' => 'required|string|max:255',
|
||||
'tipologia' => 'required|in:avatar,galleria,documento,statuto,altro',
|
||||
'file' => 'required|file|max:10240',
|
||||
'visibilita' => 'required|in:pubblico,individuo,gruppo',
|
||||
'visibilita_target_id' => 'nullable|integer',
|
||||
'visibilita_target_type' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$path = $file->store('documenti', 'local');
|
||||
|
||||
$visibilitaTargetType = null;
|
||||
if ($data['visibilita'] === 'individuo' && $data['visibilita_target_id']) {
|
||||
$visibilitaTargetType = Individuo::class;
|
||||
} elseif ($data['visibilita'] === 'gruppo' && $data['visibilita_target_id']) {
|
||||
$visibilitaTargetType = Gruppo::class;
|
||||
}
|
||||
|
||||
Documento::create([
|
||||
'nome_file' => $data['nome_file'],
|
||||
'file_path' => $path,
|
||||
'tipo' => 'upload',
|
||||
'tipologia' => $data['tipologia'],
|
||||
'visibilita' => $data['visibilita'],
|
||||
'visibilita_target_id' => $data['visibilita_target_id'] ?? null,
|
||||
'visibilita_target_type' => $visibilitaTargetType,
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'dimensione' => $file->getSize(),
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
$redirect = $request->_redirect ?? url()->previous();
|
||||
return redirect($redirect)->with('success', 'Documento caricato.');
|
||||
}
|
||||
|
||||
public function download($documento)
|
||||
{
|
||||
$this->authorizeRead('documenti');
|
||||
$documento = Documento::findOrFail($documento);
|
||||
|
||||
if (!Storage::disk('local')->exists($documento->file_path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
|
||||
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
|
||||
|
||||
return Storage::disk('local')->download(
|
||||
$documento->file_path,
|
||||
$filename,
|
||||
['Content-Type' => $documento->mime_type]
|
||||
);
|
||||
}
|
||||
|
||||
public function preview($documento)
|
||||
{
|
||||
$this->authorizeRead('documenti');
|
||||
$documento = Documento::findOrFail($documento);
|
||||
|
||||
if (!Storage::disk('local')->exists($documento->file_path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$fullPath = Storage::disk('local')->path($documento->file_path);
|
||||
$mimeType = $documento->mime_type;
|
||||
|
||||
if (str_starts_with($mimeType, 'image/')) {
|
||||
return response()->file($fullPath, ['Content-Type' => $mimeType]);
|
||||
}
|
||||
|
||||
return response()->file($fullPath, ['Content-Type' => $mimeType]);
|
||||
}
|
||||
|
||||
public function destroy($documento)
|
||||
{
|
||||
$this->authorizeDelete('documenti');
|
||||
$documento = Documento::findOrFail($documento);
|
||||
|
||||
if (Storage::disk('local')->exists($documento->file_path)) {
|
||||
Storage::disk('local')->delete($documento->file_path);
|
||||
}
|
||||
|
||||
$documento->delete();
|
||||
|
||||
$redirect = request('_redirect', url('/'));
|
||||
return redirect($redirect)->with('success', 'Documento eliminato.');
|
||||
}
|
||||
|
||||
public function checkLinks($documento)
|
||||
{
|
||||
$documento = Documento::findOrFail($documento);
|
||||
$links = [];
|
||||
|
||||
if (in_array($documento->visibilita, ['individuo', 'gruppo', 'evento']) && $documento->visibilita_target_id) {
|
||||
if ($documento->visibilita === 'individuo') {
|
||||
$target = Individuo::find($documento->visibilita_target_id);
|
||||
if ($target) {
|
||||
$links[] = [
|
||||
'type' => 'individuo',
|
||||
'name' => $target->cognome . ' ' . $target->nome,
|
||||
'url' => '/individui/' . $target->id,
|
||||
];
|
||||
}
|
||||
} elseif ($documento->visibilita === 'gruppo') {
|
||||
$target = Gruppo::find($documento->visibilita_target_id);
|
||||
if ($target) {
|
||||
$links[] = [
|
||||
'type' => 'gruppo',
|
||||
'name' => $target->nome,
|
||||
'url' => '/gruppi/' . $target->id,
|
||||
];
|
||||
}
|
||||
} elseif ($documento->visibilita === 'evento') {
|
||||
$target = Evento::find($documento->visibilita_target_id);
|
||||
if ($target) {
|
||||
$links[] = [
|
||||
'type' => 'evento',
|
||||
'name' => $target->nome_evento,
|
||||
'url' => '/eventi/' . $target->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($documento->tipologia === 'avatar') {
|
||||
$linkedToAvatar = Individuo::whereHas('avatar', function($q) use ($documento) {
|
||||
$q->where('id', $documento->id);
|
||||
})->count();
|
||||
if ($linkedToAvatar > 0) {
|
||||
$links[] = [
|
||||
'type' => 'avatar',
|
||||
'name' => 'Avatar di ' . $linkedToAvatar . ' individuo(i)',
|
||||
'url' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($documento->eventi()->count() > 0) {
|
||||
foreach ($documento->eventi as $evento) {
|
||||
$links[] = [
|
||||
'type' => 'evento',
|
||||
'name' => 'Evento: ' . $evento->nome_evento,
|
||||
'url' => '/eventi/' . $evento->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'has_links' => count($links) > 0,
|
||||
'links' => $links,
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkMassLinks(Request $request)
|
||||
{
|
||||
$idsInput = $request->input('ids', '');
|
||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||
|
||||
if (empty($ids)) {
|
||||
return response()->json(['linked_count' => 0, 'links' => []]);
|
||||
}
|
||||
|
||||
$documenti = Documento::whereIn('id', $ids)->get();
|
||||
$allLinks = [];
|
||||
$linkedCount = 0;
|
||||
|
||||
foreach ($documenti as $documento) {
|
||||
$isLinked = false;
|
||||
$linkName = '';
|
||||
$linkUrl = null;
|
||||
|
||||
if (in_array($documento->visibilita, ['individuo', 'gruppo', 'evento']) && $documento->visibilita_target_id) {
|
||||
$isLinked = true;
|
||||
$linkName = $documento->nome_file . ' → ';
|
||||
|
||||
if ($documento->visibilita === 'individuo') {
|
||||
$target = Individuo::find($documento->visibilita_target_id);
|
||||
$linkName .= $target ? $target->cognome . ' ' . $target->nome : 'Individuo #' . $documento->visibilita_target_id;
|
||||
$linkUrl = $target ? '/individui/' . $target->id : null;
|
||||
} elseif ($documento->visibilita === 'gruppo') {
|
||||
$target = Gruppo::find($documento->visibilita_target_id);
|
||||
$linkName .= $target ? $target->nome : 'Gruppo #' . $documento->visibilita_target_id;
|
||||
$linkUrl = $target ? '/gruppi/' . $target->id : null;
|
||||
} elseif ($documento->visibilita === 'evento') {
|
||||
$target = Evento::find($documento->visibilita_target_id);
|
||||
$linkName .= $target ? $target->nome_evento : 'Evento #' . $documento->visibilita_target_id;
|
||||
$linkUrl = $target ? '/eventi/' . $target->id : null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($documento->tipologia === 'avatar') {
|
||||
$linkedToAvatar = Individuo::whereHas('avatar', function($q) use ($documento) {
|
||||
$q->where('id', $documento->id);
|
||||
})->count();
|
||||
if ($linkedToAvatar > 0) {
|
||||
$isLinked = true;
|
||||
$allLinks[] = [
|
||||
'name' => $documento->nome_file . ' → Avatar di ' . $linkedToAvatar . ' individuo(i)',
|
||||
'url' => null,
|
||||
];
|
||||
$linkedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($documento->eventi()->count() > 0) {
|
||||
$eventi = $documento->eventi;
|
||||
foreach ($eventi as $evento) {
|
||||
$allLinks[] = [
|
||||
'name' => $documento->nome_file . ' → Evento: ' . $evento->nome_evento,
|
||||
'url' => '/eventi/' . $evento->id,
|
||||
];
|
||||
$linkedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isLinked && $documento->tipologia !== 'avatar' && $linkName) {
|
||||
$allLinks[] = [
|
||||
'name' => $linkName,
|
||||
'url' => $linkUrl,
|
||||
];
|
||||
$linkedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'linked_count' => $linkedCount,
|
||||
'links' => $allLinks,
|
||||
]);
|
||||
}
|
||||
|
||||
public function massDestroy(Request $request)
|
||||
{
|
||||
$this->authorizeDelete('documenti');
|
||||
$idsInput = $request->input('ids', '');
|
||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||
|
||||
if (empty($ids)) {
|
||||
return back()->with('error', 'Nessun documento selezionato.');
|
||||
}
|
||||
|
||||
$documenti = Documento::whereIn('id', $ids)->get();
|
||||
|
||||
foreach ($documenti as $documento) {
|
||||
if (Storage::disk('local')->exists($documento->file_path)) {
|
||||
Storage::disk('local')->delete($documento->file_path);
|
||||
}
|
||||
$documento->delete();
|
||||
}
|
||||
|
||||
return back()->with('success', count($ids) . ' documenti eliminati.');
|
||||
}
|
||||
|
||||
public function massUpdate(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$idsInput = $request->input('ids', '');
|
||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||
|
||||
if (empty($ids)) {
|
||||
return back()->with('error', 'Nessun documento selezionato.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'tipologia' => 'nullable|string',
|
||||
'visibilita' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$tipologia = $data['tipologia'] ?? null;
|
||||
$visibilita = $data['visibilita'] ?? null;
|
||||
|
||||
if (empty($tipologia) && empty($visibilita)) {
|
||||
return back()->with('error', 'Specificare almeno un campo da aggiornare.');
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
if (!empty($tipologia)) {
|
||||
$updateData['tipologia'] = $tipologia;
|
||||
}
|
||||
if (!empty($visibilita)) {
|
||||
$updateData['visibilita'] = $visibilita;
|
||||
$typeMap = [
|
||||
'individuo' => Individuo::class,
|
||||
'gruppo' => Gruppo::class,
|
||||
'evento' => Evento::class,
|
||||
'mailing' => \App\Models\MailingList::class,
|
||||
];
|
||||
if (isset($typeMap[$visibilita])) {
|
||||
$updateData['visibilita_target_type'] = $typeMap[$visibilita];
|
||||
}
|
||||
}
|
||||
|
||||
Documento::whereIn('id', $ids)->update($updateData);
|
||||
|
||||
return back()->with('success', count($ids) . ' documenti aggiornati.');
|
||||
}
|
||||
|
||||
public function massAssociate(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$idsInput = $request->input('ids', '');
|
||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||
|
||||
if (empty($ids)) {
|
||||
return back()->with('error', 'Nessun documento selezionato.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'target_type' => 'required|in:individuo,gruppo,evento,mailing',
|
||||
'target_id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$targetType = match($data['target_type']) {
|
||||
'individuo' => Individuo::class,
|
||||
'gruppo' => Gruppo::class,
|
||||
'evento' => Evento::class,
|
||||
'mailing' => \App\Models\MailingList::class,
|
||||
};
|
||||
|
||||
Documento::whereIn('id', $ids)->update([
|
||||
'visibilita' => $data['target_type'],
|
||||
'visibilita_target_id' => $data['target_id'],
|
||||
'visibilita_target_type' => $targetType,
|
||||
]);
|
||||
|
||||
return back()->with('success', count($ids) . ' documenti associati.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EmailFolder;
|
||||
use App\Models\EmailMessage;
|
||||
use App\Models\EmailAttachment;
|
||||
use App\Models\EmailSetting;
|
||||
use App\Models\MailingList;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Documento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class EmailController extends Controller
|
||||
{
|
||||
public function index(Request $request, $folder = 'inbox')
|
||||
{
|
||||
$this->authorizeRead('email');
|
||||
if ($folder === 'compose' || $folder === 'nuovo') {
|
||||
return $this->compose($request);
|
||||
}
|
||||
|
||||
$folderModel = EmailFolder::where('type', $folder)->first();
|
||||
|
||||
if (!$folderModel) {
|
||||
$folderModel = EmailFolder::where('type', 'inbox')->first();
|
||||
if (!$folderModel) {
|
||||
$this->ensureFoldersExist();
|
||||
$folderModel = EmailFolder::where('type', 'inbox')->first();
|
||||
}
|
||||
}
|
||||
|
||||
$query = EmailMessage::where('email_folder_id', $folderModel->id);
|
||||
|
||||
if ($request->search) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('subject', 'like', '%' . $request->search . '%')
|
||||
->orWhere('from_email', 'like', '%' . $request->search . '%')
|
||||
->orWhere('to_email', 'like', '%' . $request->search . '%')
|
||||
->orWhere('body_text', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$sortField = $request->sort ?? 'received_at';
|
||||
$sortDir = $request->direction ?? 'desc';
|
||||
|
||||
$allowedSorts = ['subject', 'received_at', 'sent_at', 'from_email', 'to_email'];
|
||||
if (!in_array($sortField, $allowedSorts)) {
|
||||
$sortField = 'received_at';
|
||||
}
|
||||
if (!in_array($sortDir, ['asc', 'desc'])) {
|
||||
$sortDir = 'desc';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField === 'sent_at' ? 'received_at' : $sortField, $sortDir);
|
||||
|
||||
$messages = $query->paginate(20)->withQueryString();
|
||||
$folders = EmailFolder::orderBy('sort_order')->get();
|
||||
$mailingLists = MailingList::where('attiva', true)->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
|
||||
$emailSettings = EmailSetting::getActive();
|
||||
$syncInterval = $emailSettings ? $emailSettings->sync_interval_minutes : 0;
|
||||
|
||||
return view('email.index', compact('messages', 'folders', 'folder', 'folderModel', 'mailingLists', 'gruppi', 'syncInterval'));
|
||||
}
|
||||
|
||||
public function show($folder, $id)
|
||||
{
|
||||
$this->authorizeRead('email');
|
||||
$message = EmailMessage::with('attachments')->findOrFail($id);
|
||||
|
||||
if (!$message->is_read) {
|
||||
$message->update(['is_read' => true]);
|
||||
}
|
||||
|
||||
$folders = EmailFolder::orderBy('sort_order')->get();
|
||||
|
||||
return view('email.show', compact('message', 'folders', 'folder'));
|
||||
}
|
||||
|
||||
public function compose(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('email');
|
||||
$folders = EmailFolder::orderBy('sort_order')->get();
|
||||
$mailingLists = MailingList::where('attiva', true)->get();
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
|
||||
$prefill = [];
|
||||
if ($request->reply_to) {
|
||||
$original = EmailMessage::find($request->reply_to);
|
||||
if ($original) {
|
||||
$prefill['to'] = $original->from_email;
|
||||
$prefill['subject'] = 'Re: ' . $original->subject;
|
||||
$prefill['body'] = "\n\n--- Messaggio originale ---\n" . ($original->body_text ?? $original->body_html);
|
||||
}
|
||||
}
|
||||
if ($request->forward) {
|
||||
$original = EmailMessage::find($request->forward);
|
||||
if ($original) {
|
||||
$prefill['subject'] = 'Fwd: ' . $original->subject;
|
||||
$prefill['body'] = "\n\n--- Messaggio inoltrato ---\n" . ($original->body_text ?? $original->body_html);
|
||||
}
|
||||
}
|
||||
|
||||
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill'));
|
||||
}
|
||||
|
||||
public function send(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('email');
|
||||
$rules = [
|
||||
'subject' => 'required',
|
||||
'body' => 'required',
|
||||
];
|
||||
|
||||
if ($request->destinatario_tipo === 'manuale' || !$request->destinatario_tipo) {
|
||||
$rules['to'] = 'required';
|
||||
}
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
$settings = EmailSetting::getActive();
|
||||
if (!$settings) {
|
||||
return back()->with('error', 'Account email non configurato.');
|
||||
}
|
||||
|
||||
$recipients = $this->resolveRecipients($request);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('Email send attempt', [
|
||||
'recipients' => $recipients,
|
||||
'recipients_count' => count($recipients),
|
||||
'subject' => $validated['subject'],
|
||||
'destinatario_tipo' => $request->destinatario_tipo,
|
||||
'to' => $validated['to'] ?? null,
|
||||
'individui' => $request->individui,
|
||||
'gruppo_id' => $request->gruppo_id,
|
||||
'mailing_list_id' => $request->mailing_list_id,
|
||||
]);
|
||||
|
||||
if (empty($recipients)) {
|
||||
return back()->with('error', 'Nessun destinatario trovato. Seleziona almeno un destinatario dal menu a tendina o inserisci un indirizzo email manuale.')->withInput();
|
||||
}
|
||||
|
||||
$imapSuccess = false;
|
||||
$imapError = null;
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
try {
|
||||
$this->sendViaImap($settings, $recipient, $validated['subject'], $validated['body'], $request->cc);
|
||||
$imapSuccess = true;
|
||||
} catch (\Exception $e) {
|
||||
$imapError = $e->getMessage();
|
||||
\Illuminate\Support\Facades\Log::error('IMAP send error', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->storeSentMessage($settings, $recipients, $validated['subject'], $validated['body'], $request->cc);
|
||||
\Illuminate\Support\Facades\Log::info('Stored sent message');
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
|
||||
return back()->with('error', 'Errore nel salvataggio: ' . $e->getMessage())->withInput();
|
||||
}
|
||||
|
||||
if (!$imapSuccess && $imapError) {
|
||||
return redirect()->route('email.index', 'sent')->with('warning', 'Email salvata in Invio ma non inviata via IMAP: ' . $imapError);
|
||||
}
|
||||
|
||||
return redirect()->route('email.index', 'sent')->with('success', 'Email inviata con successo.');
|
||||
}
|
||||
|
||||
public function storeDraft(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('email');
|
||||
$settings = EmailSetting::getActive();
|
||||
$folder = EmailFolder::where('type', 'drafts')->first();
|
||||
|
||||
$message = EmailMessage::create([
|
||||
'email_folder_id' => $folder->id,
|
||||
'subject' => $request->subject ?? '(senza oggetto)',
|
||||
'body_text' => $request->body,
|
||||
'to_email' => $request->to,
|
||||
'is_draft' => true,
|
||||
'from_email' => $settings->email_address ?? '',
|
||||
'from_name' => $settings->email_name ?? '',
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true, 'draft_id' => $message->id]);
|
||||
}
|
||||
|
||||
public function bulkAction(Request $request)
|
||||
{
|
||||
try {
|
||||
$this->authorizeWrite('email');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Non hai i permessi per questa azione'
|
||||
], 403);
|
||||
}
|
||||
|
||||
$ids = $request->input('ids', []);
|
||||
$action = $request->input('action');
|
||||
|
||||
if ($action === 'empty_trash') {
|
||||
$trashFolder = EmailFolder::where('type', 'trash')->first();
|
||||
if ($trashFolder) {
|
||||
EmailMessage::where('email_folder_id', $trashFolder->id)->delete();
|
||||
}
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
return response()->json(['success' => false, 'message' => 'Nessuna email selezionata']);
|
||||
}
|
||||
|
||||
$messages = EmailMessage::whereIn('id', $ids)->get();
|
||||
|
||||
switch ($action) {
|
||||
case 'mark_read':
|
||||
$messages->each->update(['is_read' => true]);
|
||||
break;
|
||||
case 'mark_unread':
|
||||
$messages->each->update(['is_read' => false]);
|
||||
break;
|
||||
case 'star':
|
||||
$messages->each->update(['is_starred' => true]);
|
||||
break;
|
||||
case 'unstar':
|
||||
$messages->each->update(['is_starred' => false]);
|
||||
break;
|
||||
case 'move_trash':
|
||||
$trashFolder = EmailFolder::where('type', 'trash')->first();
|
||||
if ($trashFolder) {
|
||||
$messages->each->update(['email_folder_id' => $trashFolder->id]);
|
||||
}
|
||||
break;
|
||||
case 'delete_permanent':
|
||||
$messages->each->delete();
|
||||
break;
|
||||
default:
|
||||
return response()->json(['success' => false, 'message' => 'Azione non valida']);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function markRead($id)
|
||||
{
|
||||
try {
|
||||
$this->authorizeWrite('email');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Non hai i permessi per questa azione'
|
||||
], 403);
|
||||
}
|
||||
$message = EmailMessage::findOrFail($id);
|
||||
$message->update(['is_read' => true]);
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function toggleStar($id)
|
||||
{
|
||||
try {
|
||||
$this->authorizeWrite('email');
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Non hai i permessi per questa azione'
|
||||
], 403);
|
||||
}
|
||||
$message = EmailMessage::findOrFail($id);
|
||||
$message->update(['is_starred' => !$message->is_starred]);
|
||||
return response()->json(['success' => true, 'is_starred' => $message->is_starred]);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->authorizeDelete('email');
|
||||
$message = EmailMessage::findOrFail($id);
|
||||
$trashFolder = EmailFolder::where('type', 'trash')->first();
|
||||
|
||||
if ($trashFolder) {
|
||||
$message->update(['is_trash' => true, 'email_folder_id' => $trashFolder->id]);
|
||||
} else {
|
||||
$message->delete();
|
||||
}
|
||||
|
||||
return back()->with('success', 'Email spostata nel cestino.');
|
||||
}
|
||||
|
||||
public function downloadAttachment($id)
|
||||
{
|
||||
$this->authorizeRead('email');
|
||||
$attachment = EmailAttachment::findOrFail($id);
|
||||
|
||||
if (!Storage::exists($attachment->file_path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return Storage::download($attachment->file_path, $attachment->filename);
|
||||
}
|
||||
|
||||
public function saveAttachmentToDocumenti(Request $request, $id, $attachmentId)
|
||||
{
|
||||
$this->authorizeWrite('email');
|
||||
$this->authorizeWrite('documenti');
|
||||
$attachment = EmailAttachment::findOrFail($attachmentId);
|
||||
|
||||
$documento = Documento::create([
|
||||
'nome_file' => $attachment->filename,
|
||||
'file_path' => $attachment->file_path,
|
||||
'tipo' => 'upload',
|
||||
'tipologia' => 'email_attachment',
|
||||
'mime_type' => $attachment->mime_type,
|
||||
'dimensione' => $attachment->size,
|
||||
'visibilita' => null,
|
||||
'note' => 'Allegato email: ' . ($attachment->message->subject ?? ''),
|
||||
]);
|
||||
|
||||
$attachment->update(['is_saved_to_documenti' => true, 'documenti_id' => $documento->id]);
|
||||
|
||||
return back()->with('success', 'Allegato salvato nei documenti.');
|
||||
}
|
||||
|
||||
public function quickSync()
|
||||
{
|
||||
try {
|
||||
$this->syncEmails();
|
||||
return response()->json(['success' => true, 'message' => 'Sincronizzazione completata']);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncEmails()
|
||||
{
|
||||
$settings = EmailSetting::getActive();
|
||||
if (!$settings) {
|
||||
throw new \Exception('Nessuna configurazione email attiva');
|
||||
}
|
||||
|
||||
$this->ensureFoldersExist();
|
||||
|
||||
$mailbox = $this->connectImap($settings);
|
||||
|
||||
$folderMappings = [
|
||||
'INBOX' => 'inbox',
|
||||
'Sent' => 'sent',
|
||||
'[Gmail]/Sent Mail' => 'sent',
|
||||
'Sent Mail' => 'sent',
|
||||
'Drafts' => 'drafts',
|
||||
'[Gmail]/Drafts' => 'drafts',
|
||||
'Trash' => 'trash',
|
||||
'[Gmail]/Trash' => 'trash',
|
||||
];
|
||||
|
||||
foreach (array_keys($folderMappings) as $imapFolderName) {
|
||||
try {
|
||||
$folder = $mailbox->folders()->find($imapFolderName);
|
||||
if (!$folder) continue;
|
||||
|
||||
$messages = $folder->messages()
|
||||
->withHeaders()
|
||||
->withBody()
|
||||
->withBodyStructure()
|
||||
->limit(100)
|
||||
->get();
|
||||
|
||||
foreach ($messages as $imapMessage) {
|
||||
$this->processImapMessage($imapMessage, $imapFolderName);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function resolveRecipients(Request $request)
|
||||
{
|
||||
$recipients = [];
|
||||
|
||||
if ($request->destinatario_tipo === 'individui' && $request->individui) {
|
||||
$individui = Individuo::whereIn('id', $request->individui)->get();
|
||||
foreach ($individui as $ind) {
|
||||
$emails = $ind->contatti()->where('tipo', 'email')->pluck('valore')->toArray();
|
||||
$recipients = array_merge($recipients, $emails);
|
||||
}
|
||||
} elseif ($request->destinatario_tipo === 'gruppo' && $request->gruppo_id) {
|
||||
$gruppo = Gruppo::with('individui.contatti')->find($request->gruppo_id);
|
||||
foreach ($gruppo->individui as $ind) {
|
||||
$emails = $ind->contatti()->where('tipo', 'email')->pluck('valore')->toArray();
|
||||
$recipients = array_merge($recipients, $emails);
|
||||
}
|
||||
} elseif ($request->destinatario_tipo === 'mailing_list' && $request->mailing_list_id) {
|
||||
$list = MailingList::with('contatti.individuo')->find($request->mailing_list_id);
|
||||
foreach ($list->contatti as $contact) {
|
||||
if ($contact->opt_in && $contact->individuo) {
|
||||
$emails = $contact->individuo->contatti()->where('tipo', 'email')->pluck('valore')->toArray();
|
||||
$recipients = array_merge($recipients, $emails);
|
||||
}
|
||||
}
|
||||
} elseif ($request->to) {
|
||||
$recipients = is_array($request->to) ? $request->to : [$request->to];
|
||||
}
|
||||
|
||||
return array_filter(array_unique($recipients));
|
||||
}
|
||||
|
||||
private function appendSignature(string $body, ?string $signature, bool $enabled): string
|
||||
{
|
||||
if (empty($signature) || !$enabled) {
|
||||
return $body;
|
||||
}
|
||||
return $body . "\n\n---\n" . $signature;
|
||||
}
|
||||
|
||||
private function sendViaImap($settings, $to, $subject, $body, $cc = null)
|
||||
{
|
||||
$bodyWithSignature = $this->appendSignature($body, $settings->getSignature(), $settings->signature_enabled ?? false);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('sendViaSMTP start', ['to' => $to, 'subject' => $subject]);
|
||||
|
||||
$smtpPassword = $settings->getDecryptedSmtpPassword() ?? $settings->getDecryptedPassword();
|
||||
|
||||
$encryption = $settings->smtp_encryption ?? 'tls';
|
||||
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||
|
||||
$dsn = sprintf(
|
||||
'%s://%s:%s@%s:%d?encryption=%s',
|
||||
$scheme,
|
||||
rawurlencode($settings->smtp_username ?? $settings->email_address),
|
||||
rawurlencode($smtpPassword),
|
||||
$settings->smtp_host ?? 'smtp.gmail.com',
|
||||
$settings->smtp_port ?? 587,
|
||||
$encryption
|
||||
);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('SMTP DSN', ['dsn' => $dsn]);
|
||||
|
||||
try {
|
||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||
|
||||
$fromName = $settings->email_name ?? '';
|
||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
||||
$fromName = 'Glastree';
|
||||
} else {
|
||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
||||
$fromName = trim($fromName);
|
||||
if (empty($fromName)) {
|
||||
$fromName = 'Glastree';
|
||||
}
|
||||
}
|
||||
|
||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
||||
|
||||
$email = (new \Symfony\Component\Mime\Email())
|
||||
->from($fromAddress)
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->text($bodyWithSignature);
|
||||
|
||||
if ($cc) {
|
||||
$email->cc($cc);
|
||||
}
|
||||
|
||||
$mailer->send($email);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('Email sent via SMTP successfully');
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error('SMTP send error', ['error' => $e->getMessage()]);
|
||||
throw new \Exception('Errore invio SMTP: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
|
||||
{
|
||||
$sentFolder = EmailFolder::where('type', 'sent')->first();
|
||||
$bodyWithSignature = $this->appendSignature($body, $settings->getSignature(), $settings->signature_enabled ?? false);
|
||||
|
||||
$message = EmailMessage::create([
|
||||
'email_folder_id' => $sentFolder->id,
|
||||
'subject' => $subject,
|
||||
'body_text' => $bodyWithSignature,
|
||||
'from_email' => $settings->email_address,
|
||||
'from_name' => $settings->email_name,
|
||||
'to_email' => implode(', ', $recipients),
|
||||
'cc' => $cc,
|
||||
'is_sent' => true,
|
||||
'sent_at' => now(),
|
||||
'is_read' => true,
|
||||
]);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function connectImap($settings)
|
||||
{
|
||||
if (!$settings->imap_host || !$settings->imap_username) {
|
||||
throw new \Exception('Configurazione IMAP non valida');
|
||||
}
|
||||
|
||||
$encryption = match ($settings->imap_encryption) {
|
||||
'ssl' => 'ssl',
|
||||
'tls' => 'tls',
|
||||
default => 'none',
|
||||
};
|
||||
|
||||
return new \DirectoryTree\ImapEngine\Mailbox([
|
||||
'host' => $settings->imap_host,
|
||||
'port' => $settings->imap_port ?? 993,
|
||||
'encryption' => $encryption,
|
||||
'validate_cert' => false,
|
||||
'username' => $settings->imap_username,
|
||||
'password' => $settings->getDecryptedPassword(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function processImapMessage($imapMessage, $imapFolder)
|
||||
{
|
||||
$folderType = match ($imapFolder) {
|
||||
'INBOX', 'inbox' => 'inbox',
|
||||
'Sent', 'sent', '[Gmail]/Sent Mail' => 'sent',
|
||||
'Drafts', 'drafts', '[Gmail]/Drafts' => 'drafts',
|
||||
'Trash', 'trash', '[Gmail]/Trash' => 'trash',
|
||||
'Archive', 'archive', '[Gmail]/All Mail' => 'archive',
|
||||
'Starred', 'starred', '[Gmail]/Starred' => 'starred',
|
||||
default => 'inbox',
|
||||
};
|
||||
|
||||
$folder = EmailFolder::where('type', $folderType)->first();
|
||||
if (!$folder) return;
|
||||
|
||||
$messageId = $imapMessage->messageId() ?? uniqid('msg_');
|
||||
|
||||
$existing = EmailMessage::where('message_id', $messageId)->first();
|
||||
if ($existing) return;
|
||||
|
||||
$from = $imapMessage->from();
|
||||
$to = $imapMessage->to();
|
||||
|
||||
$message = EmailMessage::create([
|
||||
'email_folder_id' => $folder->id,
|
||||
'message_id' => $messageId,
|
||||
'subject' => $imapMessage->subject() ?? '(senza oggetto)',
|
||||
'from_name' => $from?->name() ?? '',
|
||||
'from_email' => $from?->email() ?? '',
|
||||
'to_email' => count($to) > 0 ? $to[0]->email() : '',
|
||||
'body_text' => $imapMessage->text() ?? '',
|
||||
'body_html' => $imapMessage->html() ?? '',
|
||||
'is_read' => $imapMessage->isSeen() ?? false,
|
||||
'is_starred' => $imapMessage->isFlagged() ?? false,
|
||||
'is_sent' => $folderType === 'sent',
|
||||
'received_at' => $imapMessage->date() ?? now(),
|
||||
'imap_uid' => $imapMessage->uid() ?? null,
|
||||
]);
|
||||
|
||||
$attachments = $imapMessage->attachments();
|
||||
if ($attachments && count($attachments) > 0) {
|
||||
foreach ($attachments as $attachment) {
|
||||
$filename = $attachment->filename() ?? 'attachment';
|
||||
$contents = $attachment->contents();
|
||||
$path = 'email-attachments/' . Str::uuid() . '_' . $filename;
|
||||
Storage::put($path, $contents);
|
||||
|
||||
EmailAttachment::create([
|
||||
'email_message_id' => $message->id,
|
||||
'filename' => $filename,
|
||||
'mime_type' => $attachment->contentType() ?? 'application/octet-stream',
|
||||
'size' => strlen($contents),
|
||||
'file_path' => $path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureFoldersExist()
|
||||
{
|
||||
$systemFolders = EmailFolder::getSystemFolders();
|
||||
|
||||
foreach ($systemFolders as $folder) {
|
||||
EmailFolder::updateOrCreate(
|
||||
['type' => $folder['type']],
|
||||
$folder
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Evento;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventoController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
$query = Evento::with(['gruppi', 'responsabili']);
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
if ($request->filled('gruppo_id')) {
|
||||
$query->whereHas('gruppi', fn($q) => $q->where('gruppi.id', $request->gruppo_id));
|
||||
}
|
||||
|
||||
if ($request->filled('tipo')) {
|
||||
$query->where('tipo_recorrenza', $request->tipo);
|
||||
}
|
||||
|
||||
$sortBy = $request->get('sort', 'created_at');
|
||||
$sortDir = $request->get('direction', 'desc');
|
||||
$allowedSorts = ['nome_evento', 'descrizione_evento', 'tipo_recorrenza', 'data_specifica', 'created_at'];
|
||||
|
||||
if ($sortBy === 'gruppi') {
|
||||
$query->withCount('gruppi')->orderBy('gruppi_count', $sortDir === 'asc' ? 'asc' : 'desc');
|
||||
} elseif ($sortBy === 'responsabili') {
|
||||
$query->withCount('responsabili')->orderBy('responsabili_count', $sortDir === 'asc' ? 'asc' : 'desc');
|
||||
} elseif (in_array($sortBy, $allowedSorts)) {
|
||||
$query->orderBy($sortBy, $sortDir === 'asc' ? 'asc' : 'desc');
|
||||
} else {
|
||||
$query->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
$eventi = $query->paginate(20);
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
|
||||
return view('eventi.index', compact('eventi', 'gruppi'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('eventi');
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
$selectedGruppo = $request->query('gruppo_id') ? Gruppo::find($request->query('gruppo_id')) : null;
|
||||
|
||||
return view('eventi.create', compact('gruppi', 'individui', 'selectedGruppo'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('eventi');
|
||||
$data = $request->validate([
|
||||
'nome_evento' => 'required|string|max:255',
|
||||
'descrizione_evento' => 'nullable|string|max:255',
|
||||
'tipo_evento' => 'nullable|string|max:100',
|
||||
'tipo_recorrenza' => 'nullable|in:singolo,settimanale,mensile,annuale,altro',
|
||||
'giorno_settimana' => 'nullable|integer|min:0|max:6',
|
||||
'giorno_mese' => 'nullable|integer|min:1|max:31',
|
||||
'occorrenza_mese' => 'nullable|string|max:10',
|
||||
'mesi_recorrenza' => 'nullable|string',
|
||||
'mese_annuale' => 'nullable|integer|min:1|max:12',
|
||||
'ora_inizio' => 'nullable|date_format:H:i',
|
||||
'data_specifica' => 'nullable|date',
|
||||
'durata_minuti' => 'nullable|integer|min:1',
|
||||
'descrizione' => 'nullable|string',
|
||||
'note' => 'nullable|string',
|
||||
'is_incontro_gruppo' => 'nullable|boolean',
|
||||
'gruppi' => 'nullable|array',
|
||||
'gruppi.*' => 'exists:gruppi,id',
|
||||
'responsabili' => 'nullable|array',
|
||||
'responsabili.*' => 'exists:individui,id',
|
||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||
'luogo_url_maps' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$evento = Evento::create([
|
||||
'nome_evento' => $data['nome_evento'],
|
||||
'descrizione_evento' => $data['descrizione_evento'] ?? null,
|
||||
'tipo_evento' => $data['tipo_evento'] ?? null,
|
||||
'tipo_recorrenza' => $data['tipo_recorrenza'] ?? 'singolo',
|
||||
'giorno_settimana' => $data['giorno_settimana'] ?? null,
|
||||
'giorno_mese' => $data['giorno_mese'] ?? null,
|
||||
'occorrenza_mese' => $data['occorrenza_mese'] ?? null,
|
||||
'mesi_recorrenza' => is_array($request->mesi_recorrenza) ? implode(',', $request->mesi_recorrenza) : ($data['mesi_recorrenza'] ?? null),
|
||||
'mese_annuale' => $data['mese_annuale'] ?? null,
|
||||
'ora_inizio' => $data['ora_inizio'] ?? null,
|
||||
'data_specifica' => $data['data_specifica'] ?? null,
|
||||
'durata_minuti' => $data['durata_minuti'] ?? null,
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'note' => $data['note'] ?? null,
|
||||
'is_incontro_gruppo' => $request->boolean('is_incontro_gruppo'),
|
||||
'luogo_indirizzo' => $data['luogo_indirizzo'] ?? null,
|
||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||
]);
|
||||
|
||||
if (!empty($data['gruppi'])) {
|
||||
$evento->gruppi()->attach($data['gruppi']);
|
||||
}
|
||||
|
||||
if (!empty($data['responsabili'])) {
|
||||
$evento->responsabili()->attach($data['responsabili']);
|
||||
}
|
||||
|
||||
return redirect('/eventi/' . $evento->id)->with('success', 'Evento creato con successo.');
|
||||
}
|
||||
|
||||
public function show($evento)
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
$evento = Evento::with(['gruppi', 'responsabili.contatti', 'documenti'])->findOrFail($evento);
|
||||
return view('eventi.show', compact('evento'));
|
||||
}
|
||||
|
||||
public function edit($evento)
|
||||
{
|
||||
$this->authorizeWrite('eventi');
|
||||
$evento = Evento::with(['gruppi', 'responsabili', 'documenti'])->findOrFail($evento);
|
||||
$gruppi = Gruppo::orderBy('nome')->get();
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
|
||||
return view('eventi.edit', compact('evento', 'gruppi', 'individui'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $evento)
|
||||
{
|
||||
$this->authorizeWrite('eventi');
|
||||
$evento = Evento::findOrFail($evento);
|
||||
|
||||
$data = $request->validate([
|
||||
'nome_evento' => 'required|string|max:255',
|
||||
'descrizione_evento' => 'nullable|string|max:255',
|
||||
'tipo_evento' => 'nullable|string|max:100',
|
||||
'tipo_recorrenza' => 'nullable|in:singolo,settimanale,mensile,annuale,altro',
|
||||
'giorno_settimana' => 'nullable|integer|min:0|max:6',
|
||||
'giorno_mese' => 'nullable|integer|min:1|max:31',
|
||||
'occorrenza_mese' => 'nullable|string|max:10',
|
||||
'mesi_recorrenza' => 'nullable|string',
|
||||
'mese_annuale' => 'nullable|integer|min:1|max:12',
|
||||
'ora_inizio' => 'nullable|date_format:H:i',
|
||||
'data_specifica' => 'nullable|date',
|
||||
'durata_minuti' => 'nullable|integer|min:1',
|
||||
'descrizione' => 'nullable|string',
|
||||
'note' => 'nullable|string',
|
||||
'is_incontro_gruppo' => 'nullable|boolean',
|
||||
'gruppi' => 'nullable|array',
|
||||
'gruppi.*' => 'exists:gruppi,id',
|
||||
'responsabili' => 'nullable|array',
|
||||
'responsabili.*' => 'exists:individui,id',
|
||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||
'luogo_url_maps' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$evento->update([
|
||||
'nome_evento' => $data['nome_evento'],
|
||||
'descrizione_evento' => $data['descrizione_evento'] ?? null,
|
||||
'tipo_evento' => $data['tipo_evento'] ?? null,
|
||||
'tipo_recorrenza' => $data['tipo_recorrenza'] ?? 'singolo',
|
||||
'giorno_settimana' => $data['giorno_settimana'] ?? null,
|
||||
'giorno_mese' => $data['giorno_mese'] ?? null,
|
||||
'occorrenza_mese' => $data['occorrenza_mese'] ?? null,
|
||||
'mesi_recorrenza' => is_array($request->mesi_recorrenza) ? implode(',', $request->mesi_recorrenza) : ($data['mesi_recorrenza'] ?? null),
|
||||
'mese_annuale' => $data['mese_annuale'] ?? null,
|
||||
'ora_inizio' => $data['ora_inizio'] ?? null,
|
||||
'data_specifica' => $data['data_specifica'] ?? null,
|
||||
'durata_minuti' => $data['durata_minuti'] ?? null,
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'note' => $data['note'] ?? null,
|
||||
'is_incontro_gruppo' => $request->boolean('is_incontro_gruppo'),
|
||||
'luogo_indirizzo' => $data['luogo_indirizzo'] ?? null,
|
||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||
]);
|
||||
|
||||
$evento->gruppi()->sync($data['gruppi'] ?? []);
|
||||
$evento->responsabili()->sync($data['responsabili'] ?? []);
|
||||
|
||||
return redirect('/eventi/' . $evento->id)->with('success', 'Evento aggiornato.');
|
||||
}
|
||||
|
||||
public function destroy($evento)
|
||||
{
|
||||
$this->authorizeDelete('eventi');
|
||||
$evento = Evento::findOrFail($evento);
|
||||
$evento->delete();
|
||||
return redirect('/eventi')->with('success', 'Evento eliminato.');
|
||||
}
|
||||
|
||||
public function massElimina(Request $request)
|
||||
{
|
||||
$this->authorizeDelete('eventi');
|
||||
|
||||
$ids = $request->input('ids', []);
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return redirect('/eventi')->with('error', 'Nessun evento selezionato.');
|
||||
}
|
||||
|
||||
$eventi = Evento::whereIn('id', $ids)->get();
|
||||
|
||||
foreach ($eventi as $evento) {
|
||||
$evento->gruppi()->detach();
|
||||
$evento->responsabili()->detach();
|
||||
$evento->delete();
|
||||
}
|
||||
|
||||
return redirect('/eventi')->with('success', count($eventi) . ' eventi eliminati con successo.');
|
||||
}
|
||||
|
||||
public function calendar()
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
return view('eventi.calendar');
|
||||
}
|
||||
|
||||
public function calendarEvents(Request $request)
|
||||
{
|
||||
$this->authorizeRead('eventi');
|
||||
|
||||
$start = $request->input('start');
|
||||
$end = $request->input('end');
|
||||
|
||||
$baseUrl = $request->getSchemeAndHttpHost();
|
||||
$query = Evento::with(['gruppi', 'responsabili']);
|
||||
|
||||
$eventi = $query->get();
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach ($eventi as $evento) {
|
||||
if ($evento->tipo_recorrenza === 'singolo' || $evento->tipo_recorrenza === null) {
|
||||
if ($evento->data_specifica) {
|
||||
$events[] = [
|
||||
'id' => $evento->id,
|
||||
'title' => $evento->nome_evento,
|
||||
'start' => $evento->data_specifica->format('Y-m-d') . ($evento->ora_inizio ? 'T' . $evento->ora_inizio->format('H:i') : ''),
|
||||
'url' => $baseUrl . '/eventi/' . $evento->id,
|
||||
'backgroundColor' => $this->getEventoColor($evento),
|
||||
'borderColor' => $this->getEventoColor($evento),
|
||||
];
|
||||
}
|
||||
} elseif ($evento->tipo_recorrenza === 'settimanale' && $evento->giorno_settimana !== null) {
|
||||
$startDate = \Carbon\Carbon::parse($start);
|
||||
$endDate = \Carbon\Carbon::parse($end);
|
||||
|
||||
$current = $startDate->copy();
|
||||
while ($current <= $endDate) {
|
||||
$nextOccurrence = $current->copy()->next($evento->giorno_settimana);
|
||||
if ($nextOccurrence >= $startDate && $nextOccurrence <= $endDate) {
|
||||
$events[] = [
|
||||
'id' => $evento->id . '_' . $nextOccurrence->format('Ymd'),
|
||||
'title' => $evento->nome_evento,
|
||||
'start' => $nextOccurrence->format('Y-m-d') . ($evento->ora_inizio ? 'T' . $evento->ora_inizio->format('H:i') : ''),
|
||||
'url' => $baseUrl . '/eventi/' . $evento->id,
|
||||
'backgroundColor' => $this->getEventoColor($evento),
|
||||
'borderColor' => $this->getEventoColor($evento),
|
||||
];
|
||||
}
|
||||
$current->addWeek();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json($events);
|
||||
}
|
||||
|
||||
private function getEventoColor(Evento $evento): string
|
||||
{
|
||||
$colors = [
|
||||
'primary' => '#007bff',
|
||||
'secondary' => '#6c757d',
|
||||
'success' => '#28a745',
|
||||
'danger' => '#dc3545',
|
||||
'warning' => '#ffc107',
|
||||
'info' => '#17a2b8',
|
||||
'dark' => '#343a40',
|
||||
];
|
||||
|
||||
if ($evento->isIncroGruppo()) {
|
||||
return $colors['info'];
|
||||
}
|
||||
|
||||
if ($evento->isRicorrente()) {
|
||||
return $colors['success'];
|
||||
}
|
||||
|
||||
return $colors['primary'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Evento;
|
||||
use App\Models\Documento;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventoDocumentoController extends Controller
|
||||
{
|
||||
public function store(Request $request, $evento)
|
||||
{
|
||||
$this->authorizeWrite('documenti');
|
||||
$this->authorizeWrite('eventi');
|
||||
$evento = Evento::findOrFail($evento);
|
||||
|
||||
$request->validate([
|
||||
'nome_file' => 'required|string|max:255',
|
||||
'tipologia' => 'required|in:documento,programma,locandina,altro',
|
||||
'file' => 'required|file|max:10240',
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$path = $file->store('documenti/eventi', 'public');
|
||||
|
||||
$documento = Documento::create([
|
||||
'nome_file' => $request->nome_file,
|
||||
'tipologia' => $request->tipologia,
|
||||
'file_path' => $path,
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'dimensione' => $file->getSize(),
|
||||
'visibilita' => 'evento',
|
||||
'visibilita_target_id' => $evento->id,
|
||||
'visibilita_target_type' => Evento::class,
|
||||
]);
|
||||
|
||||
$evento->documenti()->attach($documento->id);
|
||||
|
||||
return redirect()->back()->with('success', 'Documento caricato con successo.');
|
||||
}
|
||||
|
||||
public function destroy($evento, $documento)
|
||||
{
|
||||
$this->authorizeDelete('documenti');
|
||||
$this->authorizeWrite('eventi');
|
||||
$evento = Evento::findOrFail($evento);
|
||||
$documento = Documento::findOrFail($documento);
|
||||
|
||||
if ($evento->documenti()->where('documenti.id', $documento->id)->exists()) {
|
||||
$evento->documenti()->detach($documento->id);
|
||||
}
|
||||
|
||||
if ($documento->visibilita === 'evento' && $documento->visibilita_target_id == $evento->id) {
|
||||
if ($documento->eventi()->count() === 0 && $documento->gruppi()->count() === 0 && $documento->individui()->count() === 0) {
|
||||
if ($documento->file_path && \Storage::disk('public')->exists($documento->file_path)) {
|
||||
\Storage::disk('public')->delete($documento->file_path);
|
||||
}
|
||||
$documento->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Documento rimosso.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Diocesi;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GruppoController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeRead('gruppi');
|
||||
|
||||
$viewMode = request('view', 'table');
|
||||
$user = auth()->user();
|
||||
|
||||
$vista = \App\Models\VistaReport::where('user_id', $user->id)
|
||||
->where('tipo', 'gruppi')
|
||||
->where('is_default', true)
|
||||
->first();
|
||||
|
||||
$allColumns = ['nome', 'descrizione', 'diocesi', 'livello', 'padre', 'membri', 'responsabili', 'indirizzo', 'citta', 'telefono', 'email'];
|
||||
$defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili'];
|
||||
|
||||
if ($vista && !empty($vista->colonne_visibili)) {
|
||||
$visibleColumns = $vista->colonne_visibili;
|
||||
} else {
|
||||
$visibleColumns = $defaultVisible;
|
||||
}
|
||||
|
||||
$allGruppi = Gruppo::with(['diocesi', 'parent', 'children', 'individui'])
|
||||
->orderBy('nome')
|
||||
->get()
|
||||
->map(function ($gruppo) {
|
||||
$depth = 0;
|
||||
$parent = $gruppo->parent;
|
||||
while ($parent) {
|
||||
$depth++;
|
||||
$parent = $parent->parent;
|
||||
}
|
||||
$gruppo->depth = $depth;
|
||||
return $gruppo;
|
||||
});
|
||||
|
||||
if ($viewMode === 'table') {
|
||||
$gruppi = $this->sortGruppiHierarchically($allGruppi);
|
||||
} else {
|
||||
$gruppi = $allGruppi;
|
||||
}
|
||||
|
||||
return view('gruppi.index', compact(
|
||||
'gruppi',
|
||||
'allColumns',
|
||||
'visibleColumns',
|
||||
'viewMode',
|
||||
'vista'
|
||||
));
|
||||
}
|
||||
|
||||
protected function sortGruppiHierarchically($gruppi)
|
||||
{
|
||||
$rootGroups = $gruppi->filter(fn($g) => $g->parent_id === null)->sortBy('nome');
|
||||
$sorted = collect();
|
||||
|
||||
foreach ($rootGroups as $root) {
|
||||
$sorted->push($root);
|
||||
$this->addChildrenRecursive($root, $gruppi, $sorted);
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
protected function addChildrenRecursive($parent, $allGruppi, &$sorted)
|
||||
{
|
||||
$children = $allGruppi->filter(fn($g) => $g->parent_id === $parent->id)->sortBy('nome');
|
||||
|
||||
foreach ($children as $child) {
|
||||
$sorted->push($child);
|
||||
$this->addChildrenRecursive($child, $allGruppi, $sorted);
|
||||
}
|
||||
}
|
||||
|
||||
$allGruppi = Gruppo::with(['diocesi', 'parent', 'children', 'individui'])
|
||||
->orderBy('nome')
|
||||
->get()
|
||||
->map(function ($gruppo) {
|
||||
$depth = 0;
|
||||
$parent = $gruppo->parent;
|
||||
while ($parent) {
|
||||
$depth++;
|
||||
$parent = $parent->parent;
|
||||
}
|
||||
$gruppo->depth = $depth;
|
||||
return $gruppo;
|
||||
});
|
||||
|
||||
if ($viewMode === 'table') {
|
||||
$gruppi = $this->sortGruppiHierarchically($allGruppi);
|
||||
} else {
|
||||
$gruppi = $allGruppi;
|
||||
}
|
||||
|
||||
return view('gruppi.index', compact(
|
||||
'gruppi',
|
||||
'allColumns',
|
||||
'visibleColumns',
|
||||
'viewMode',
|
||||
'vista'
|
||||
));
|
||||
}
|
||||
|
||||
protected function sortGruppiHierarchically($gruppi)
|
||||
{
|
||||
$rootGroups = $gruppi->filter(fn($g) => $g->parent_id === null)->sortBy('nome');
|
||||
$sorted = collect();
|
||||
|
||||
foreach ($rootGroups as $root) {
|
||||
$sorted->push($root);
|
||||
$this->addChildrenRecursive($root, $gruppi, $sorted);
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
protected function addChildrenRecursive($parent, $allGruppi, &$sorted)
|
||||
{
|
||||
$children = $allGruppi->filter(fn($g) => $g->parent_id === $parent->id)->sortBy('nome');
|
||||
|
||||
foreach ($children as $child) {
|
||||
$sorted->push($child);
|
||||
$this->addChildrenRecursive($child, $allGruppi, $sorted);
|
||||
}
|
||||
}
|
||||
|
||||
$allGruppi = Gruppo::with(['diocesi', 'parent', 'children', 'individui'])
|
||||
->orderBy('nome')
|
||||
->get()
|
||||
->map(function ($gruppo) {
|
||||
$depth = 0;
|
||||
$parent = $gruppo->parent;
|
||||
while ($parent) {
|
||||
$depth++;
|
||||
$parent = $parent->parent;
|
||||
}
|
||||
$gruppo->depth = $depth;
|
||||
return $gruppo;
|
||||
});
|
||||
|
||||
if ($viewMode === 'table') {
|
||||
$gruppi = $this->sortGruppiHierarchically($allGruppi);
|
||||
} else {
|
||||
$gruppi = $allGruppi;
|
||||
}
|
||||
|
||||
return view('gruppi.index', compact(
|
||||
'gruppi',
|
||||
'allColumns',
|
||||
'visibleColumns',
|
||||
'viewMode',
|
||||
'vista'
|
||||
));
|
||||
}
|
||||
|
||||
protected function sortGruppiHierarchically($gruppi)
|
||||
{
|
||||
$rootGroups = $gruppi->filter(fn($g) => $g->parent_id === null)->sortBy('nome');
|
||||
$sorted = collect();
|
||||
|
||||
foreach ($rootGroups as $root) {
|
||||
$sorted->push($root);
|
||||
$this->addChildrenRecursive($root, $gruppi, $sorted);
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
protected function addChildrenRecursive($parent, $allGruppi, &$sorted)
|
||||
{
|
||||
$children = $allGruppi->filter(fn($g) => $g->parent_id === $parent->id)->sortBy('nome');
|
||||
|
||||
foreach ($children as $child) {
|
||||
$sorted->push($child);
|
||||
$this->addChildrenRecursive($child, $allGruppi, $sorted);
|
||||
}
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$diocesi = Diocesi::orderBy('nome')->get();
|
||||
$allGruppi = Gruppo::orderBy('nome')->get();
|
||||
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
|
||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||
return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'parent_id' => 'nullable|exists:gruppi,id',
|
||||
'diocesi_id' => 'nullable|exists:diocesi,id',
|
||||
'responsabile_ids' => 'nullable|array',
|
||||
'responsabile_ids.*' => 'exists:individui,id',
|
||||
'indirizzo_incontro' => 'nullable|string|max:500',
|
||||
'cap_incontro' => 'nullable|string|max:10',
|
||||
'città_incontro' => 'nullable|string|max:255',
|
||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||
'mappa_posizione' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (!empty($data['responsabile_ids'])) {
|
||||
$data['responsabile_ids'] = json_encode(array_values($data['responsabile_ids']));
|
||||
}
|
||||
|
||||
$gruppo = Gruppo::create($data);
|
||||
|
||||
if ($request->has('individui')) {
|
||||
foreach ($request->individui as $individuoData) {
|
||||
if (!empty($individuoData['individuo_id'])) {
|
||||
$gruppo->individui()->attach($individuoData['individuo_id'], [
|
||||
'ruolo_nel_gruppo' => $individuoData['ruolo_nel_gruppo'] ?? null,
|
||||
'data_adesione' => $individuoData['data_adesione'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect('/gruppi/' . $gruppo->id)->with('success', 'Gruppo creato con successo.');
|
||||
}
|
||||
|
||||
public function show($gruppo)
|
||||
{
|
||||
$this->authorizeRead('gruppi');
|
||||
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
|
||||
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
||||
}, 'eventi' => function ($q) {
|
||||
$q->where('is_incontro_gruppo', true);
|
||||
}, 'eventi.responsabili.contatti'])->findOrFail($gruppo);
|
||||
return view('gruppi.show', compact('gruppo'));
|
||||
}
|
||||
|
||||
public function edit($gruppo)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti'])->findOrFail($gruppo);
|
||||
$diocesi = Diocesi::orderBy('nome')->get();
|
||||
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
|
||||
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
|
||||
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $gruppo)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$gruppo = Gruppo::findOrFail($gruppo);
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'parent_id' => 'nullable|exists:gruppi,id',
|
||||
'diocesi_id' => 'nullable|exists:diocesi,id',
|
||||
'responsabile_ids' => 'nullable|array',
|
||||
'responsabile_ids.*' => 'exists:individui,id',
|
||||
'indirizzo_incontro' => 'nullable|string|max:500',
|
||||
'cap_incontro' => 'nullable|string|max:10',
|
||||
'città_incontro' => 'nullable|string|max:255',
|
||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||
'mappa_posizione' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (!empty($data['responsabile_ids'])) {
|
||||
$data['responsabile_ids'] = json_encode(array_values($data['responsabile_ids']));
|
||||
} else {
|
||||
$data['responsabile_ids'] = null;
|
||||
}
|
||||
|
||||
$gruppo->update($data);
|
||||
|
||||
if ($request->has('individui')) {
|
||||
$gruppo->individui()->detach();
|
||||
foreach ($request->individui as $individuoData) {
|
||||
if (!empty($individuoData['individuo_id'])) {
|
||||
$gruppo->individui()->attach($individuoData['individuo_id'], [
|
||||
'ruolo_ids' => !empty($individuoData['ruolo_ids']) ? json_encode(array_values($individuoData['ruolo_ids'])) : null,
|
||||
'data_adesione' => $individuoData['data_adesione'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect('/gruppi/' . $gruppo->id)->with('success', 'Gruppo aggiornato.');
|
||||
}
|
||||
|
||||
public function destroy($gruppo)
|
||||
{
|
||||
$this->authorizeDelete('gruppi');
|
||||
$gruppo = Gruppo::with('children', 'individui')->findOrFail($gruppo);
|
||||
|
||||
$hasChildren = $gruppo->children()->count() > 0;
|
||||
$isSuperAdmin = auth()->user()->isSuperAdmin();
|
||||
|
||||
if ($hasChildren && !$isSuperAdmin) {
|
||||
return redirect('/gruppi')->with('error', 'Solo il superamministratore può eliminare un gruppo con sottogruppi.');
|
||||
}
|
||||
|
||||
if ($hasChildren && $isSuperAdmin) {
|
||||
foreach ($gruppo->children as $child) {
|
||||
$this->destroyGruppo($child);
|
||||
}
|
||||
}
|
||||
|
||||
$this->destroyGruppo($gruppo);
|
||||
|
||||
return redirect('/gruppi')->with('success', 'Gruppo eliminato.');
|
||||
}
|
||||
|
||||
protected function destroyGruppo(Gruppo $gruppo): void
|
||||
{
|
||||
$gruppo->individui()->detach();
|
||||
$gruppo->eventi()->detach();
|
||||
foreach ($gruppo->documenti as $documento) {
|
||||
if ($documento->file_path && file_exists(storage_path('app/public/' . $documento->file_path))) {
|
||||
unlink(storage_path('app/public/' . $documento->file_path));
|
||||
}
|
||||
}
|
||||
$gruppo->documenti()->delete();
|
||||
$gruppo->delete();
|
||||
}
|
||||
|
||||
public function allGruppi()
|
||||
{
|
||||
$this->authorizeRead('gruppi');
|
||||
$gruppi = Gruppo::select('id', 'nome')
|
||||
->orderBy('nome')
|
||||
->get()
|
||||
->map(function($g) {
|
||||
return [
|
||||
'id' => $g->id,
|
||||
'nome' => $g->nome,
|
||||
'full_path' => $g->getFullPathAttribute(),
|
||||
];
|
||||
});
|
||||
return response()->json($gruppi);
|
||||
}
|
||||
|
||||
public function individuiEmail(Gruppo $gruppo)
|
||||
{
|
||||
$this->authorizeRead('gruppi');
|
||||
$individui = $gruppo->individui()->with('contatti')->get();
|
||||
|
||||
$result = $individui->map(function($ind) {
|
||||
$emails = $ind->contatti->where('tipo', 'email')->pluck('valore')->toArray();
|
||||
return [
|
||||
'id' => $ind->id,
|
||||
'codice_id' => $ind->codice_id,
|
||||
'cognome' => $ind->cognome,
|
||||
'nome' => $ind->nome,
|
||||
'emails' => $emails,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function saveVista(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'colonne_visibili' => 'nullable|array',
|
||||
'colonne_visibili.*' => 'string',
|
||||
'is_default' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
if (!empty($data['is_default'])) {
|
||||
\App\Models\VistaReport::where('user_id', auth()->id())
|
||||
->where('tipo', 'gruppi')
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$vista = \App\Models\VistaReport::create([
|
||||
'user_id' => auth()->id(),
|
||||
'nome' => $data['nome'],
|
||||
'tipo' => 'gruppi',
|
||||
'colonne_visibili' => $data['colonne_visibili'] ?? [],
|
||||
'is_default' => !empty($data['is_default']),
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true, 'vista_id' => $vista->id]);
|
||||
}
|
||||
|
||||
public function deleteVista($vista)
|
||||
{
|
||||
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||
->where('id', $vista)
|
||||
->firstOrFail();
|
||||
$vista->delete();
|
||||
|
||||
return redirect()->route('gruppi.index')->with('success', 'Vista eliminata.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Gruppo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GruppoIndividuoController extends Controller
|
||||
{
|
||||
public function store(Request $request, $individuo)
|
||||
{
|
||||
$this->authorizeWrite('individui');
|
||||
$this->authorizeWrite('gruppi');
|
||||
$data = $request->validate([
|
||||
'gruppo_id' => 'required|exists:gruppi,id',
|
||||
'ruolo_ids' => 'nullable|array',
|
||||
'ruolo_ids.*' => 'exists:ruoli,id',
|
||||
'data_adesione' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$individuo = \App\Models\Individuo::findOrFail($individuo);
|
||||
$gruppo = Gruppo::findOrFail($data['gruppo_id']);
|
||||
|
||||
if ($individuo->gruppi->contains($gruppo->id)) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Già associato a questo gruppo.'], 422);
|
||||
}
|
||||
return redirect()->back()->with('error', 'Già associato a questo gruppo.');
|
||||
}
|
||||
|
||||
$individuo->gruppi()->attach($gruppo->id, [
|
||||
'ruolo_ids' => !empty($data['ruolo_ids']) ? json_encode(array_values($data['ruolo_ids'])) : null,
|
||||
'data_adesione' => $data['data_adesione'] ?? null,
|
||||
]);
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => 'Gruppo associato.']);
|
||||
}
|
||||
return redirect()->back()->with('success', 'Gruppo associato.');
|
||||
}
|
||||
|
||||
public function update(Request $request, $individuo, $gruppo)
|
||||
{
|
||||
$this->authorizeWrite('individui');
|
||||
$this->authorizeWrite('gruppi');
|
||||
$data = $request->validate([
|
||||
'ruolo_ids' => 'nullable|array',
|
||||
'ruolo_ids.*' => 'exists:ruoli,id',
|
||||
'data_adesione' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$individuo = \App\Models\Individuo::findOrFail($individuo);
|
||||
$gruppoModel = Gruppo::findOrFail($gruppo);
|
||||
|
||||
$individuo->gruppi()->updateExistingPivot($gruppoModel->id, [
|
||||
'ruolo_ids' => !empty($data['ruolo_ids']) ? json_encode(array_values($data['ruolo_ids'])) : null,
|
||||
'data_adesione' => $data['data_adesione'] ?? null,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Associazione aggiornata.');
|
||||
}
|
||||
|
||||
public function destroy($individuo, $gruppo)
|
||||
{
|
||||
$this->authorizeDelete('individui');
|
||||
$this->authorizeWrite('gruppi');
|
||||
$individuo = \App\Models\Individuo::findOrFail($individuo);
|
||||
$individuo->gruppi()->detach($gruppo);
|
||||
|
||||
if (request()->ajax() || request()->expectsJson()) {
|
||||
return response()->json(['success' => 'Rimosso dal gruppo.']);
|
||||
}
|
||||
return redirect()->back()->with('success', 'Rimosso dal gruppo.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Ruolo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GruppoMembroController extends Controller
|
||||
{
|
||||
public function store(Request $request, $gruppo)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$this->authorizeRead('individui');
|
||||
$gruppo = Gruppo::findOrFail($gruppo);
|
||||
|
||||
$data = $request->validate([
|
||||
'individuo_id' => 'required|exists:individui,id',
|
||||
'ruolo_ids' => 'nullable|array',
|
||||
'ruolo_ids.*' => 'exists:ruoli,id',
|
||||
'data_adesione' => 'nullable|date',
|
||||
]);
|
||||
|
||||
if ($gruppo->individui()->where('individuo_id', $data['individuo_id'])->exists()) {
|
||||
if ($request->ajax() || $request->expectsJson()) {
|
||||
return response()->json(['error' => 'Questo individuo è già membro di questo gruppo.'], 422);
|
||||
}
|
||||
return redirect()->back()->with('error', 'Questo individuo è già membro di questo gruppo.');
|
||||
}
|
||||
|
||||
$gruppo->individui()->attach($data['individuo_id'], [
|
||||
'ruolo_ids' => !empty($data['ruolo_ids']) ? json_encode(array_values($data['ruolo_ids'])) : null,
|
||||
'data_adesione' => $data['data_adesione'] ?? null,
|
||||
]);
|
||||
|
||||
if ($request->ajax() || $request->expectsJson()) {
|
||||
return response()->json(['success' => 'Membro aggiunto con successo.']);
|
||||
}
|
||||
return redirect()->back()->with('success', 'Membro aggiunto con successo.');
|
||||
}
|
||||
|
||||
public function update(Request $request, $gruppo, $individuo)
|
||||
{
|
||||
$this->authorizeWrite('gruppi');
|
||||
$gruppo = Gruppo::findOrFail($gruppo);
|
||||
|
||||
$data = $request->validate([
|
||||
'ruolo_ids' => 'nullable|array',
|
||||
'ruolo_ids.*' => 'exists:ruoli,id',
|
||||
'data_adesione' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$gruppo->individui()->updateExistingPivot($individuo, [
|
||||
'ruolo_ids' => !empty($data['ruolo_ids']) ? json_encode(array_values($data['ruolo_ids'])) : null,
|
||||
'data_adesione' => $data['data_adesione'] ?? null,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Membro aggiornato con successo.');
|
||||
}
|
||||
|
||||
public function destroy($gruppo, $individuo)
|
||||
{
|
||||
$this->authorizeDelete('gruppi');
|
||||
$gruppo = Gruppo::findOrFail($gruppo);
|
||||
$gruppo->individui()->detach($individuo);
|
||||
|
||||
if (request()->ajax() || request()->expectsJson()) {
|
||||
return response()->json(['success' => 'Membro rimosso dal gruppo.']);
|
||||
}
|
||||
return redirect()->back()->with('success', 'Membro rimosso dal gruppo.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Notifica;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$stats = [
|
||||
'individui' => Individuo::count(),
|
||||
'gruppi' => Gruppo::count(),
|
||||
'notifiche' => $user->unreadNotificheCount(),
|
||||
];
|
||||
|
||||
$notifiche = $user->notifiche()->take(5)->get();
|
||||
|
||||
return view('home.dashboard', compact('stats', 'notifiche'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AppSetting;
|
||||
use App\Models\Ruolo;
|
||||
use App\Models\TipologiaDocumento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImpostazioniController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
||||
$ruoli = Ruolo::orderBy('ordine')->get();
|
||||
$appSettings = AppSetting::first();
|
||||
|
||||
return view('impostazioni.index', compact('tipologie', 'ruoli', 'appSettings'));
|
||||
}
|
||||
|
||||
public function saveAppSettings(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome_applicazione' => 'nullable|string|max:100',
|
||||
'nome_organizzazione' => 'nullable|string|max:255',
|
||||
'footer_text' => 'nullable|string|max:255',
|
||||
'app_version' => 'nullable|string|max:50',
|
||||
'dashboard_welcome' => 'nullable|string|max:1000',
|
||||
'show_version' => 'boolean',
|
||||
]);
|
||||
|
||||
$settings = AppSetting::first() ?? new AppSetting();
|
||||
|
||||
$settings->nome_applicazione = $validated['nome_applicazione'] ?? null;
|
||||
$settings->nome_organizzazione = $validated['nome_organizzazione'] ?? null;
|
||||
$settings->footer_text = $validated['footer_text'] ?? null;
|
||||
$settings->app_version = $validated['app_version'] ?? null;
|
||||
$settings->dashboard_welcome = $validated['dashboard_welcome'] ?? null;
|
||||
$settings->show_version = $request->boolean('show_version');
|
||||
|
||||
$settings->save();
|
||||
|
||||
return redirect('/impostazioni#applicazione')->with('success', 'Impostazioni applicazione salvate.');
|
||||
}
|
||||
|
||||
public function tipologieStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:tipologie_documenti,nome',
|
||||
'descrizione' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$maxOrdine = TipologiaDocumento::max('ordine') ?? 0;
|
||||
|
||||
TipologiaDocumento::create([
|
||||
'nome' => $validated['nome'],
|
||||
'descrizione' => $validated['descrizione'] ?? null,
|
||||
'ordine' => $maxOrdine + 1,
|
||||
'attiva' => true,
|
||||
]);
|
||||
|
||||
return redirect('/impostazioni#tipologie')->with('success', 'Tipologia aggiunta.');
|
||||
}
|
||||
|
||||
public function tipologieUpdate(Request $request, $id)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$tipologia = TipologiaDocumento::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:tipologie_documenti,nome,' . $id,
|
||||
'descrizione' => 'nullable|string|max:255',
|
||||
'attiva' => 'boolean',
|
||||
]);
|
||||
|
||||
$tipologia->update($validated);
|
||||
|
||||
return redirect('/impostazioni#tipologie')->with('success', 'Tipologia aggiornata.');
|
||||
}
|
||||
|
||||
public function tipologieDestroy($id)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
$tipologia = TipologiaDocumento::findOrFail($id);
|
||||
|
||||
$count = $tipologia->documenti()->count();
|
||||
if ($count > 0) {
|
||||
return redirect('/impostazioni#tipologie')->with('error', 'Impossibile eliminare: ' . $count . ' documenti associati.');
|
||||
}
|
||||
|
||||
$tipologia->delete();
|
||||
|
||||
return redirect('/impostazioni#tipologie')->with('success', 'Tipologia eliminata.');
|
||||
}
|
||||
|
||||
public function tipologieReorder(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$order = $request->input('order', []);
|
||||
|
||||
foreach ($order as $index => $id) {
|
||||
TipologiaDocumento::where('id', $id)->update(['ordine' => $index]);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function ruoliStore(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:ruoli,nome',
|
||||
'descrizione' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$maxOrdine = Ruolo::max('ordine') ?? 0;
|
||||
|
||||
Ruolo::create([
|
||||
'nome' => $validated['nome'],
|
||||
'descrizione' => $validated['descrizione'] ?? null,
|
||||
'ordine' => $maxOrdine + 1,
|
||||
'attiva' => true,
|
||||
]);
|
||||
|
||||
return redirect('/impostazioni#ruoli')->with('success', 'Ruolo aggiunto.');
|
||||
}
|
||||
|
||||
public function ruoliUpdate(Request $request, $id)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$ruolo = Ruolo::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nome' => 'required|string|max:50|unique:ruoli,nome,' . $id,
|
||||
'descrizione' => 'nullable|string|max:255',
|
||||
'attiva' => 'boolean',
|
||||
]);
|
||||
|
||||
$ruolo->update($validated);
|
||||
|
||||
return redirect('/impostazioni#ruoli')->with('success', 'Ruolo aggiornato.');
|
||||
}
|
||||
|
||||
public function ruoliDestroy($id)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
|
||||
$ruolo = Ruolo::findOrFail($id);
|
||||
|
||||
$count = \DB::table('gruppo_individuo')
|
||||
->whereNotNull('ruolo_ids')
|
||||
->whereRaw("JSON_CONTAINS(ruolo_ids, ?)", [json_encode($id)])
|
||||
->count();
|
||||
|
||||
if ($count > 0) {
|
||||
return redirect('/impostazioni#ruoli')->with('error', 'Impossibile eliminare: ' . $count . ' membri associati a questo ruolo.');
|
||||
}
|
||||
|
||||
$ruolo->delete();
|
||||
|
||||
return redirect('/impostazioni#ruoli')->with('success', 'Ruolo eliminato.');
|
||||
}
|
||||
|
||||
public function ruoliReorder(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$order = $request->input('order', []);
|
||||
|
||||
foreach ($order as $index => $id) {
|
||||
Ruolo::where('id', $id)->update(['ordine' => $index]);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function uploadLogo(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$request->validate([
|
||||
'logo' => 'nullable|image|mimes:png,jpg,jpeg,gif,svg|max:2048',
|
||||
'logo_small' => 'nullable|image|mimes:png,jpg,jpeg,gif,svg|max:512',
|
||||
]);
|
||||
|
||||
$settings = AppSetting::first() ?? new AppSetting();
|
||||
|
||||
if ($request->hasFile('logo')) {
|
||||
$path = $request->file('logo')->store('logos', 'public');
|
||||
$settings->logo_path = $path;
|
||||
}
|
||||
|
||||
if ($request->hasFile('logo_small')) {
|
||||
$path = $request->file('logo_small')->store('logos', 'public');
|
||||
$settings->logo_small_path = $path;
|
||||
}
|
||||
|
||||
if ($settings->id) {
|
||||
$settings->save();
|
||||
} else {
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
return redirect('/impostazioni#logo')->with('success', 'Logo caricato con successo.');
|
||||
}
|
||||
|
||||
public function removeLogo(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
|
||||
$type = $request->input('type');
|
||||
|
||||
$settings = AppSetting::first();
|
||||
|
||||
if ($settings) {
|
||||
if ($type === 'logo') {
|
||||
if ($settings->logo_path) {
|
||||
Storage::disk('public')->delete($settings->logo_path);
|
||||
$settings->logo_path = null;
|
||||
}
|
||||
} elseif ($type === 'logo_small') {
|
||||
if ($settings->logo_small_path) {
|
||||
Storage::disk('public')->delete($settings->logo_small_path);
|
||||
$settings->logo_small_path = null;
|
||||
}
|
||||
}
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Contatto;
|
||||
use App\Models\Documento;
|
||||
use App\Models\Comune;
|
||||
use App\Models\Diocesi;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class IndividuoController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorizeRead('individui');
|
||||
$individui = Individuo::with('contatti')->orderBy('cognome')->orderBy('nome')->paginate(20);
|
||||
|
||||
$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'],
|
||||
];
|
||||
|
||||
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono'];
|
||||
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||
? $vista->colonne_visibili
|
||||
: $defaultVisible;
|
||||
|
||||
return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns'));
|
||||
}
|
||||
|
||||
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();
|
||||
return view('individui.create', compact('comuni', 'diocesi'));
|
||||
}
|
||||
|
||||
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',
|
||||
]);
|
||||
|
||||
$individuo = Individuo::create($data);
|
||||
|
||||
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'])->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'])->findOrFail($individuo);
|
||||
return view('individui.edit', compact('individuo'));
|
||||
}
|
||||
|
||||
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',
|
||||
]);
|
||||
|
||||
$individuo->update($data);
|
||||
|
||||
\Log::info('Individuo dati salvati', ['id' => $individuo->id]);
|
||||
|
||||
$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()]);
|
||||
|
||||
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 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]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MailingList;
|
||||
use App\Models\MailingContact;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Documento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MailingController extends Controller
|
||||
{
|
||||
public function nuovo(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$individui = collect();
|
||||
$individuiIds = $request->input('individui');
|
||||
|
||||
if ($individuiIds) {
|
||||
$ids = array_map('trim', explode(',', $individuiIds));
|
||||
$individui = Individuo::with('contatti')->whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
$liste = MailingList::all();
|
||||
$documenti = Documento::orderBy('nome_file')->get();
|
||||
|
||||
$documentiSelezionati = collect();
|
||||
if ($request->has('documenti_selezionati')) {
|
||||
$docIds = array_filter(array_map('trim', explode(',', $request->input('documenti_selezionati'))));
|
||||
$documentiSelezionati = Documento::whereIn('id', $docIds)->get();
|
||||
}
|
||||
|
||||
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati'));
|
||||
}
|
||||
|
||||
public function invia(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$data = $request->validate([
|
||||
'oggetto' => 'required|string|max:255',
|
||||
'corpo' => 'required',
|
||||
'lista_id' => 'nullable|exists:mailing_liste,id',
|
||||
'destinatari_ids' => 'nullable|string',
|
||||
'documenti_selezionati' => 'nullable|string',
|
||||
'allegato' => 'nullable|file|max:10240',
|
||||
]);
|
||||
|
||||
$documentiAllegati = [];
|
||||
|
||||
if ($request->hasFile('allegato')) {
|
||||
$file = $request->file('allegato');
|
||||
$nomeOriginale = $file->getClientOriginalName();
|
||||
$path = $file->store('documenti/allegati');
|
||||
|
||||
$documento = Documento::create([
|
||||
'nome_file' => $nomeOriginale,
|
||||
'file_path' => $path,
|
||||
'tipo' => 'allegato',
|
||||
'tipologia' => 'allegato_email',
|
||||
'visibilita' => 'pubblico',
|
||||
'dimensione' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
$documentiAllegati[] = $documento->id;
|
||||
}
|
||||
|
||||
if (!empty($data['documenti_selezionati'])) {
|
||||
$docIds = array_filter(array_map('trim', explode(',', $data['documenti_selezionati'])));
|
||||
$documentiAllegati = array_merge($documentiAllegati, $docIds);
|
||||
}
|
||||
|
||||
$destinatari = [];
|
||||
if (!empty($data['destinatari_ids'])) {
|
||||
$ids = array_filter(array_map('trim', explode(',', $data['destinatari_ids'])));
|
||||
$destinatari = Individuo::with('contatti')->whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
return back()->with('success', 'Messaggio preparato per ' . count($destinatari) . ' destinatari con ' . count($documentiAllegati) . ' allegato(i).');
|
||||
}
|
||||
|
||||
public function invio(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$liste = MailingList::where('attiva', true)->orderBy('nome')->get();
|
||||
$documenti = Documento::orderBy('nome_file')->get();
|
||||
|
||||
return view('mailing.invio', compact('liste', 'documenti'));
|
||||
}
|
||||
|
||||
public function invioElabora(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$data = $request->validate([
|
||||
'liste' => 'required|array',
|
||||
'liste.*' => 'exists:mailing_liste,id',
|
||||
'oggetto' => 'required|string|max:255',
|
||||
'corpo' => 'required',
|
||||
'documenti_selezionati' => 'nullable|array',
|
||||
'documenti_selezionati.*' => 'exists:documenti,id',
|
||||
'allegato' => 'nullable|file|max:10240',
|
||||
]);
|
||||
|
||||
$documentiAllegati = [];
|
||||
|
||||
if ($request->hasFile('allegato')) {
|
||||
$file = $request->file('allegato');
|
||||
$nomeOriginale = $file->getClientOriginalName();
|
||||
$path = $file->store('documenti/allegati');
|
||||
|
||||
$documento = Documento::create([
|
||||
'nome_file' => $nomeOriginale,
|
||||
'file_path' => $path,
|
||||
'tipo' => 'allegato',
|
||||
'tipologia' => 'allegato_email',
|
||||
'visibilita' => 'pubblico',
|
||||
'dimensione' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
$documentiAllegati[] = $documento->id;
|
||||
}
|
||||
|
||||
if (!empty($data['documenti_selezionati'])) {
|
||||
$documentiAllegati = array_merge($documentiAllegati, $data['documenti_selezionati']);
|
||||
}
|
||||
|
||||
$contatti = MailingContact::whereIn('mailing_list_id', $data['liste'])
|
||||
->where('opt_in', true)
|
||||
->with('individuo.contatti')
|
||||
->get();
|
||||
|
||||
$destinatari = $contatti->map(function($c) {
|
||||
return $c->individuo->contatti->where('tipo', 'email')->first()?->valore;
|
||||
})->filter()->unique()->values();
|
||||
|
||||
return back()->with('success', 'Messaggio pronto per l\'invio a ' . $destinatari->count() . ' destinatari unici da ' . count($data['liste']) . ' lista(e).');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MailingList;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MailingListController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeRead('mailing');
|
||||
$mailingLists = MailingList::with(['user', 'contatti'])
|
||||
->orderBy('nome')
|
||||
->get();
|
||||
return view('mailing-liste.index', compact('mailingLists'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
return view('mailing-liste.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'attiva' => 'boolean',
|
||||
'contatti_json' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$data['user_id'] = auth()->id();
|
||||
$data['attiva'] = $data['attiva'] ?? true;
|
||||
|
||||
$lista = MailingList::create([
|
||||
'nome' => $data['nome'],
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'attiva' => $data['attiva'],
|
||||
'user_id' => $data['user_id'],
|
||||
]);
|
||||
|
||||
if (!empty($data['contatti_json'])) {
|
||||
$contatti = json_decode($data['contatti_json'], true);
|
||||
if (is_array($contatti)) {
|
||||
foreach ($contatti as $contatto) {
|
||||
if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) {
|
||||
$lista->contatti()->create([
|
||||
'individuo_id' => $contatto['individuo_id'],
|
||||
'opt_in' => true,
|
||||
'opt_in_data' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('mailing-liste.index')->with('success', 'Lista creata con ' . $lista->contatti()->count() . ' contatti.');
|
||||
}
|
||||
|
||||
public function show($mailing_liste)
|
||||
{
|
||||
$this->authorizeRead('mailing');
|
||||
$mailingList = MailingList::with(['contatti.individuo.contatti'])->findOrFail($mailing_liste);
|
||||
return view('mailing-liste.show', compact('mailingList'));
|
||||
}
|
||||
|
||||
public function edit($mailingList)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$mailingList = MailingList::with('contatti.individuo.contatti')->findOrFail($mailingList);
|
||||
return view('mailing-liste.edit', compact('mailingList'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $mailingList)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$mailingList = MailingList::findOrFail($mailingList);
|
||||
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'attiva' => 'boolean',
|
||||
'contatti_json' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$data['attiva'] = $data['attiva'] ?? false;
|
||||
|
||||
$mailingList->update([
|
||||
'nome' => $data['nome'],
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'attiva' => $data['attiva'],
|
||||
]);
|
||||
|
||||
if (!empty($data['contatti_json'])) {
|
||||
$newContatti = json_decode($data['contatti_json'], true);
|
||||
if (is_array($newContatti)) {
|
||||
$existingIds = $mailingList->contatti()->pluck('individuo_id')->toArray();
|
||||
$newIds = array_column($newContatti, 'individuo_id');
|
||||
|
||||
$toRemove = array_diff($existingIds, $newIds);
|
||||
if (!empty($toRemove)) {
|
||||
$mailingList->contatti()->whereIn('individuo_id', $toRemove)->delete();
|
||||
}
|
||||
|
||||
$toAdd = array_diff($newIds, $existingIds);
|
||||
foreach ($newContatti as $contatto) {
|
||||
if (in_array($contatto['individuo_id'], $toAdd) && !empty($contatto['email'])) {
|
||||
$mailingList->contatti()->create([
|
||||
'individuo_id' => $contatto['individuo_id'],
|
||||
'opt_in' => true,
|
||||
'opt_in_data' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('mailing-liste.show', $mailingList->id)->with('success', 'Lista aggiornata.');
|
||||
}
|
||||
|
||||
public function destroy($mailingList)
|
||||
{
|
||||
$this->authorizeDelete('mailing');
|
||||
$mailingList = MailingList::findOrFail($mailingList);
|
||||
$mailingList->delete();
|
||||
return redirect()->route('mailing-liste.index')->with('success', 'Lista eliminata.');
|
||||
}
|
||||
|
||||
public function massElimina(Request $request)
|
||||
{
|
||||
$this->authorizeDelete('mailing');
|
||||
|
||||
$ids = $request->input('ids', []);
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return redirect()->route('mailing-liste.index')->with('error', 'Nessuna mailing list selezionata.');
|
||||
}
|
||||
|
||||
$liste = MailingList::whereIn('id', $ids)->get();
|
||||
|
||||
foreach ($liste as $lista) {
|
||||
$lista->contatti()->delete();
|
||||
$lista->delete();
|
||||
}
|
||||
|
||||
return redirect()->route('mailing-liste.index')->with('success', count($liste) . ' mailing list eliminate con successo.');
|
||||
}
|
||||
|
||||
public function createFromIndividui(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('mailing');
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'descrizione' => 'nullable|string',
|
||||
'contatti' => 'required|array',
|
||||
'contatti.*.individuo_id' => 'required|exists:individui,id',
|
||||
'contatti.*.email' => 'required|email',
|
||||
]);
|
||||
|
||||
$lista = MailingList::create([
|
||||
'nome' => $data['nome'],
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'attiva' => true,
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
foreach ($data['contatti'] as $contatto) {
|
||||
$lista->contatti()->create([
|
||||
'individuo_id' => $contatto['individuo_id'],
|
||||
'opt_in' => true,
|
||||
'opt_in_data' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'lista_id' => $lista->id]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Evento;
|
||||
use App\Models\Documento;
|
||||
use App\Models\MailingList;
|
||||
use App\Models\Contatto;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
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 = \App\Models\ReportCustom::where('user_id', auth()->id())
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return view('report.index', compact('stats', 'prebuiltReports', 'customReports'));
|
||||
}
|
||||
|
||||
public function run(Request $request)
|
||||
{
|
||||
if (!auth()->user()->canAccess('report')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$reportType = $request->input('report');
|
||||
|
||||
$data = 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(),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$data) {
|
||||
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
||||
}
|
||||
|
||||
return view('report.result', compact('reportType', 'data'));
|
||||
}
|
||||
|
||||
public function runCustom($id)
|
||||
{
|
||||
if (!auth()->user()->canAccess('report')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$report = \App\Models\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' => 'nullable|array',
|
||||
]);
|
||||
|
||||
\App\Models\ReportCustom::create([
|
||||
'user_id' => auth()->id(),
|
||||
'nome' => $data['nome'],
|
||||
'descrizione' => $data['descrizione'] ?? null,
|
||||
'tipo_report' => $data['tipo_report'],
|
||||
'config' => $data['config'] ?? [],
|
||||
]);
|
||||
|
||||
return redirect()->route('report.index')->with('success', 'Report personalizzato creato.');
|
||||
}
|
||||
|
||||
public function destroyCustom($id)
|
||||
{
|
||||
if (!auth()->user()->canAccess('report')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$report = \App\Models\ReportCustom::where('user_id', auth()->id())->findOrFail($id);
|
||||
$report->delete();
|
||||
|
||||
return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.');
|
||||
}
|
||||
|
||||
public function exportCSV(Request $request)
|
||||
{
|
||||
if (!auth()->user()->canAccess('report')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$reportType = $request->input('report');
|
||||
|
||||
$data = 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(),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$data) {
|
||||
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
||||
}
|
||||
|
||||
$response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) {
|
||||
$handle = fopen('php://output', 'w');
|
||||
|
||||
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="report_' . $reportType . '_' . now()->format('Y-m-d') . '.csv"');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
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',
|
||||
],
|
||||
[
|
||||
'id' => 'individui_per_eta',
|
||||
'nome' => 'Individui per Fascia d\'Età',
|
||||
'descrizione' => 'Distribuzione degli individui per fasce d\'età',
|
||||
'icona' => 'fa-birthday-cake',
|
||||
'colore' => 'success',
|
||||
],
|
||||
[
|
||||
'id' => 'gruppi_gerarchia',
|
||||
'nome' => 'Gerarchia Gruppi',
|
||||
'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli',
|
||||
'icona' => 'fa-sitemap',
|
||||
'colore' => 'warning',
|
||||
],
|
||||
[
|
||||
'id' => 'gruppi_membri',
|
||||
'nome' => 'Gruppi e Membri',
|
||||
'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri',
|
||||
'icona' => 'fa-users',
|
||||
'colore' => 'primary',
|
||||
],
|
||||
[
|
||||
'id' => 'eventi_calendario',
|
||||
'nome' => 'Eventi del Mese',
|
||||
'descrizione' => 'Tutti gli eventi programmati per il mese corrente',
|
||||
'icona' => 'fa-calendar-alt',
|
||||
'colore' => 'danger',
|
||||
],
|
||||
[
|
||||
'id' => 'documenti_per_tipo',
|
||||
'nome' => 'Documenti per Tipologia',
|
||||
'descrizione' => 'Distribuzione dei documenti per tipologia',
|
||||
'icona' => 'fa-file-alt',
|
||||
'colore' => 'secondary',
|
||||
],
|
||||
[
|
||||
'id' => 'contatti_per_tipo',
|
||||
'nome' => 'Contatti per Tipo',
|
||||
'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)',
|
||||
'icona' => 'fa-address-book',
|
||||
'colore' => 'teal',
|
||||
],
|
||||
[
|
||||
'id' => 'mailing_list_dettaglio',
|
||||
'nome' => 'Dettaglio Mailing List',
|
||||
'descrizione' => 'Elenco completo delle mailing list con i contatti associati',
|
||||
'icona' => 'fa-envelope',
|
||||
'colore' => 'purple',
|
||||
],
|
||||
[
|
||||
'id' => 'individui_senza_contatti',
|
||||
'nome' => 'Individui senza Contatti',
|
||||
'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato',
|
||||
'icona' => 'fa-user-slash',
|
||||
'colore' => 'dark',
|
||||
],
|
||||
[
|
||||
'id' => 'individui_senza_gruppo',
|
||||
'nome' => 'Individui senza Gruppo',
|
||||
'descrizione' => 'Lista degli individui non associati a nessun gruppo',
|
||||
'icona' => 'fa-user-friends',
|
||||
'colore' => 'orange',
|
||||
],
|
||||
[
|
||||
'id' => 'eventi_per_gruppo',
|
||||
'nome' => 'Eventi per Gruppo',
|
||||
'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza',
|
||||
'icona' => 'fa-calendar-check',
|
||||
'colore' => 'pink',
|
||||
],
|
||||
[
|
||||
'id' => 'documenti_orfani',
|
||||
'nome' => 'Documenti Orfani',
|
||||
'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento',
|
||||
'icona' => 'fa-file-excel',
|
||||
'colore' => 'gray',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
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', 'children.children'])->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($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($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->email ?? '-',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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 runCustomReport(\App\Models\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' => [],
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($config['search'])) {
|
||||
$search = $config['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
foreach ($config['search_columns'] ?? [] as $col) {
|
||||
$q->orWhere($col, 'like', '%' . $search . '%');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($config['sort_by'])) {
|
||||
$query->orderBy($config['sort_by'], $config['sort_direction'] ?? 'asc');
|
||||
}
|
||||
|
||||
if (!empty($config['limit'])) {
|
||||
$query->limit((int) $config['limit']);
|
||||
}
|
||||
|
||||
$results = $query->get();
|
||||
|
||||
$headers = !empty($config['columns']) ? $config['columns'] : array_keys($results->first()?->toArray() ?? []);
|
||||
|
||||
$rows = $results->map(function ($item) use ($headers) {
|
||||
$row = [];
|
||||
foreach ($headers as $col) {
|
||||
$value = $item->{$col} ?? '';
|
||||
if ($value instanceof \Carbon\Carbon) {
|
||||
$value = $value->format('d/m/Y');
|
||||
}
|
||||
$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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\VistaReport;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VistaReportController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->authorizeRead('viste');
|
||||
$viste = VistaReport::where('user_id', auth()->id())
|
||||
->orderBy('tipo')
|
||||
->orderBy('nome')
|
||||
->get();
|
||||
|
||||
return view('viste.index', compact('viste'));
|
||||
}
|
||||
|
||||
public function showJson(VistaReport $vistaReport)
|
||||
{
|
||||
$this->authorizeRead('viste');
|
||||
if ($vistaReport->user_id !== auth()->id()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return response()->json($vistaReport);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'tipo' => 'required|in:individui,gruppi,documenti,eventi',
|
||||
'colonne_visibili' => 'nullable|array',
|
||||
'colonne_ordinamento' => 'nullable|array',
|
||||
'filtri' => 'nullable|array',
|
||||
'ricerca' => 'nullable|string',
|
||||
'is_default' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$isDefault = filter_var($data['is_default'] ?? false, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
|
||||
|
||||
if ($isDefault) {
|
||||
VistaReport::where('user_id', auth()->id())
|
||||
->where('tipo', $data['tipo'])
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$vista = VistaReport::create([
|
||||
'user_id' => auth()->id(),
|
||||
'nome' => $data['nome'],
|
||||
'tipo' => $data['tipo'],
|
||||
'colonne_visibili' => $data['colonne_visibili'] ?? null,
|
||||
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
|
||||
'filtri' => $data['filtri'] ?? null,
|
||||
'ricerca' => $data['ricerca'] ?? null,
|
||||
'is_default' => $isDefault,
|
||||
]);
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true, 'message' => 'Vista salvata.']);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Vista salvata.');
|
||||
}
|
||||
|
||||
public function update(Request $request, VistaReport $vistaReport)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
if ($vistaReport->user_id !== auth()->id()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'nome' => 'required|string|max:255',
|
||||
'colonne_visibili' => 'nullable|array',
|
||||
'colonne_ordinamento' => 'nullable|array',
|
||||
'filtri' => 'nullable|array',
|
||||
'ricerca' => 'nullable|string',
|
||||
'is_default' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
if (!empty($data['is_default'])) {
|
||||
VistaReport::where('user_id', auth()->id())
|
||||
->where('tipo', $vistaReport->tipo)
|
||||
->where('id', '!=', $vistaReport->id)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$vistaReport->update($data);
|
||||
|
||||
return back()->with('success', 'Vista aggiornata.');
|
||||
}
|
||||
|
||||
public function setDefault(Request $request, VistaReport $vistaReport)
|
||||
{
|
||||
$this->authorizeWrite('viste');
|
||||
if ($vistaReport->user_id !== auth()->id()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
VistaReport::where('user_id', auth()->id())
|
||||
->where('tipo', $vistaReport->tipo)
|
||||
->update(['is_default' => false]);
|
||||
|
||||
$vistaReport->update(['is_default' => true]);
|
||||
|
||||
return back()->with('success', 'Vista predefinita impostata.');
|
||||
}
|
||||
|
||||
public static function getDefaultForUser($tipo)
|
||||
{
|
||||
return VistaReport::where('user_id', auth()->id())
|
||||
->where('tipo', $tipo)
|
||||
->where('is_default', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function destroy(VistaReport $vistaReport)
|
||||
{
|
||||
$this->authorizeDelete('viste');
|
||||
if ($vistaReport->user_id !== auth()->id()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$vistaReport->delete();
|
||||
|
||||
return back()->with('success', 'Vista eliminata.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AdminOnly
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
if (!auth()->user()->isSuperAdmin()) {
|
||||
abort(403, 'Accesso riservato agli amministratori.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckPermission
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string $module, string $level = 'read'): Response
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->isSuperAdmin()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$requiredLevel = match ($level) {
|
||||
'read' => 1,
|
||||
'write', 'create', 'edit' => 2,
|
||||
'delete' => 2,
|
||||
default => 1,
|
||||
};
|
||||
|
||||
if (!$user->hasPermission($module, $requiredLevel)) {
|
||||
abort(403, 'Permesso insufficiente per questa operazione.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceHttps
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (env('FORCE_HTTPS', false)) {
|
||||
$scheme = $request->headers->get('X-Forwarded-Proto', $request->getScheme());
|
||||
if ($scheme === 'https' && !$request->isSecure()) {
|
||||
return redirect()->secure($request->getRequestUri());
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user