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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ActivityLog extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'action',
|
||||
'module',
|
||||
'description',
|
||||
'details',
|
||||
'ip_address',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'details' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public static function log(
|
||||
string $action,
|
||||
string $module,
|
||||
?string $description = null,
|
||||
?array $details = null
|
||||
): self {
|
||||
return static::create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => $action,
|
||||
'module' => $module,
|
||||
'description' => $description,
|
||||
'details' => $details,
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function logCreate(Model $model, ?string $description = null): self
|
||||
{
|
||||
return static::log(
|
||||
'create',
|
||||
$model::class,
|
||||
$description ?? 'Creato ' . class_basename($model),
|
||||
['new' => $model->toArray()]
|
||||
);
|
||||
}
|
||||
|
||||
public static function logUpdate(Model $model, array $old, ?string $description = null): self
|
||||
{
|
||||
return static::log(
|
||||
'update',
|
||||
$model::class,
|
||||
$description ?? 'Modificato ' . class_basename($model),
|
||||
['old' => $old, 'new' => $model->toArray()]
|
||||
);
|
||||
}
|
||||
|
||||
public static function logDelete(Model $model, ?string $description = null): self
|
||||
{
|
||||
return static::log(
|
||||
'delete',
|
||||
$model::class,
|
||||
$description ?? 'Eliminato ' . class_basename($model),
|
||||
['deleted' => $model->toArray()]
|
||||
);
|
||||
}
|
||||
|
||||
public static function logLogin(): self
|
||||
{
|
||||
return static::log('login', 'auth', 'Login effettuato');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AppSetting extends Model
|
||||
{
|
||||
protected $table = 'app_settings';
|
||||
|
||||
protected $fillable = [
|
||||
'logo',
|
||||
'logo_small',
|
||||
'logo_path',
|
||||
'logo_small_path',
|
||||
'nome_applicazione',
|
||||
'nome_organizzazione',
|
||||
'footer_text',
|
||||
'app_version',
|
||||
'dashboard_welcome',
|
||||
'show_version',
|
||||
];
|
||||
|
||||
public static function getSetting(string $key, $default = null)
|
||||
{
|
||||
$setting = self::first();
|
||||
return $setting?->{$key} ?? $default;
|
||||
}
|
||||
|
||||
public static function getLogo(): ?string
|
||||
{
|
||||
return self::getSetting('logo');
|
||||
}
|
||||
|
||||
public static function getLogoSmall(): ?string
|
||||
{
|
||||
return self::getSetting('logo_small');
|
||||
}
|
||||
|
||||
public static function getAppName(): string
|
||||
{
|
||||
return self::getSetting('nome_applicazione', 'Glastree');
|
||||
}
|
||||
|
||||
public static function getOrgName(): string
|
||||
{
|
||||
return self::getSetting('nome_organizzazione', 'Parrocchia');
|
||||
}
|
||||
|
||||
public static function getFooterText(): string
|
||||
{
|
||||
$footer = self::getSetting('footer_text');
|
||||
if ($footer) {
|
||||
return $footer;
|
||||
}
|
||||
$appName = self::getAppName();
|
||||
$year = date('Y');
|
||||
return "© {$year} {$appName}";
|
||||
}
|
||||
|
||||
public static function getAppVersion(): ?string
|
||||
{
|
||||
return self::getSetting('app_version');
|
||||
}
|
||||
|
||||
public static function getDashboardWelcome(): ?string
|
||||
{
|
||||
return self::getSetting('dashboard_welcome');
|
||||
}
|
||||
|
||||
public static function shouldShowVersion(): bool
|
||||
{
|
||||
return (bool) self::getSetting('show_version', false);
|
||||
}
|
||||
|
||||
public function getLogoUrlInstance(): ?string
|
||||
{
|
||||
if ($this->logo && file_exists(public_path('storage/' . $this->logo))) {
|
||||
return asset('storage/' . $this->logo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLogoSmallUrlInstance(): ?string
|
||||
{
|
||||
if ($this->logo_small && file_exists(public_path('storage/' . $this->logo_small))) {
|
||||
return asset('storage/' . $this->logo_small);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLogoPath(): ?string
|
||||
{
|
||||
if ($this->logo_path && file_exists(public_path('storage/' . $this->logo_path))) {
|
||||
return asset('storage/' . $this->logo_path);
|
||||
}
|
||||
if ($this->logo && file_exists(public_path('storage/' . $this->logo))) {
|
||||
return asset('storage/' . $this->logo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLogoSmallPath(): ?string
|
||||
{
|
||||
if ($this->logo_small_path && file_exists(public_path('storage/' . $this->logo_small_path))) {
|
||||
return asset('storage/' . $this->logo_small_path);
|
||||
}
|
||||
if ($this->logo_small && file_exists(public_path('storage/' . $this->logo_small))) {
|
||||
return asset('storage/' . $this->logo_small);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getLogoUrl(): ?string
|
||||
{
|
||||
$setting = self::first();
|
||||
return $setting?->getLogoPath();
|
||||
}
|
||||
|
||||
public static function getLogoSmallUrl(): ?string
|
||||
{
|
||||
$setting = self::first();
|
||||
return $setting?->getLogoSmallPath();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Comune extends Model
|
||||
{
|
||||
protected $table = 'comuni';
|
||||
protected $fillable = ['nome', 'codice_istat', 'cap', 'sigla_provincia', 'regione', 'latitudine', 'longitudine'];
|
||||
|
||||
public static function search(string $query)
|
||||
{
|
||||
return static::where('nome', 'like', "%{$query}%")
|
||||
->orderBy('nome')
|
||||
->limit(20)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Contatto extends Model
|
||||
{
|
||||
protected $table = 'contatti';
|
||||
protected $fillable = ['individuo_id', 'tipo', 'valore', 'etichetta', 'is_primary'];
|
||||
|
||||
protected $casts = [
|
||||
'is_primary' => 'boolean',
|
||||
];
|
||||
|
||||
public function individuo(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Individuo::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Diocesi extends Model
|
||||
{
|
||||
protected $table = 'diocesi';
|
||||
protected $fillable = ['nome', 'regione'];
|
||||
|
||||
public function gruppi(): HasMany
|
||||
{
|
||||
return $this->hasMany(Gruppo::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Documento extends Model
|
||||
{
|
||||
protected $table = 'documenti';
|
||||
protected $fillable = [
|
||||
'tenant_id', 'user_id', 'nome_file', 'file_path', 'tipo', 'tipologia',
|
||||
'visibilita', 'visibilita_target_id', 'visibilita_target_type',
|
||||
'mime_type', 'dimensione', 'note'
|
||||
];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function target(): MorphTo
|
||||
{
|
||||
return $this->morphTo('target', 'visibilita_target_type', 'visibilita_target_id');
|
||||
}
|
||||
|
||||
public function eventi(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Evento::class, 'eventi_documenti')->withTimestamps();
|
||||
}
|
||||
|
||||
public function gruppi(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Gruppo::class, 'gruppi_documenti')->withTimestamps();
|
||||
}
|
||||
|
||||
public function individui(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Individuo::class, 'individui_documenti')->withTimestamps();
|
||||
}
|
||||
|
||||
public function isVisibilePerIndividuo(Individuo $individuo): bool
|
||||
{
|
||||
if ($this->visibilita === 'pubblico') return true;
|
||||
|
||||
if ($this->visibilita === 'individuo' && $this->visibilita_target_id === $individuo->id) return true;
|
||||
|
||||
if ($this->visibilita === 'gruppo' && $this->visibilita_target_type === Gruppo::class) {
|
||||
return $individuo->gruppi()->where('gruppi.id', $this->visibilita_target_id)->exists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class EmailAttachment extends Model
|
||||
{
|
||||
protected $table = 'email_attachments';
|
||||
|
||||
protected $fillable = [
|
||||
'email_message_id', 'filename', 'mime_type', 'size', 'file_path',
|
||||
'is_saved_to_documenti', 'documenti_id'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_saved_to_documenti' => 'boolean',
|
||||
];
|
||||
|
||||
public function message(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EmailMessage::class, 'email_message_id');
|
||||
}
|
||||
|
||||
public function documento(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Documento::class, 'documenti_id');
|
||||
}
|
||||
|
||||
public function getUrlAttribute(): string
|
||||
{
|
||||
return route('email.attachment.download', $this->id);
|
||||
}
|
||||
|
||||
public function saveToDocumenti(): ?Documento
|
||||
{
|
||||
if ($this->is_saved_to_documenti || !$this->documenti_id) {
|
||||
return $this->documento;
|
||||
}
|
||||
|
||||
$documento = Documento::create([
|
||||
'nome_file' => $this->filename,
|
||||
'file_path' => $this->file_path,
|
||||
'tipo' => 'upload',
|
||||
'tipologia' => 'email_attachment',
|
||||
'mime_type' => $this->mime_type,
|
||||
'dimensione' => $this->size,
|
||||
'note' => 'Allegato email: ' . ($this->message->subject ?? ''),
|
||||
]);
|
||||
|
||||
$this->update(['is_saved_to_documenti' => true, 'documenti_id' => $documento->id]);
|
||||
|
||||
return $documento;
|
||||
}
|
||||
|
||||
public function getIconAttribute(): string
|
||||
{
|
||||
if (str_starts_with($this->mime_type, 'image/')) {
|
||||
return 'fa-image';
|
||||
}
|
||||
if (str_starts_with($this->mime_type, 'video/')) {
|
||||
return 'fa-video';
|
||||
}
|
||||
if (str_starts_with($this->mime_type, 'audio/')) {
|
||||
return 'fa-music';
|
||||
}
|
||||
if (str_contains($this->mime_type, 'pdf')) {
|
||||
return 'fa-file-pdf';
|
||||
}
|
||||
if (str_contains($this->mime_type, 'word')) {
|
||||
return 'fa-file-word';
|
||||
}
|
||||
if (str_contains($this->mime_type, 'excel') || str_contains($this->mime_type, 'spreadsheet')) {
|
||||
return 'fa-file-excel';
|
||||
}
|
||||
return 'fa-file';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class EmailFolder extends Model
|
||||
{
|
||||
protected $table = 'email_folders';
|
||||
|
||||
protected $fillable = ['name', 'imap_folder_name', 'type', 'icon', 'color', 'sort_order', 'is_system'];
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'boolean',
|
||||
];
|
||||
|
||||
public function messages(): HasMany
|
||||
{
|
||||
return $this->hasMany(EmailMessage::class, 'email_folder_id')->orderBy('received_at', 'desc');
|
||||
}
|
||||
|
||||
public function unreadCount(): int
|
||||
{
|
||||
return $this->messages()->where('is_read', false)->count();
|
||||
}
|
||||
|
||||
public static function getSystemFolders(): array
|
||||
{
|
||||
return [
|
||||
['name' => 'Inbox', 'imap_folder_name' => 'INBOX', 'type' => 'inbox', 'icon' => 'fa-inbox', 'color' => 'primary', 'sort_order' => 0, 'is_system' => true],
|
||||
['name' => 'Invio', 'imap_folder_name' => 'Sent', 'type' => 'sent', 'icon' => 'fa-paper-plane', 'color' => 'success', 'sort_order' => 1, 'is_system' => true],
|
||||
['name' => 'Bozze', 'imap_folder_name' => 'Drafts', 'type' => 'drafts', 'icon' => 'fa-edit', 'color' => 'warning', 'sort_order' => 2, 'is_system' => true],
|
||||
['name' => 'Cestino', 'imap_folder_name' => 'Trash', 'type' => 'trash', 'icon' => 'fa-trash', 'color' => 'danger', 'sort_order' => 3, 'is_system' => true],
|
||||
['name' => 'Archivio', 'imap_folder_name' => 'Archive', 'type' => 'archive', 'icon' => 'fa-archive', 'color' => 'secondary', 'sort_order' => 4, 'is_system' => true],
|
||||
['name' => 'Importanti', 'imap_folder_name' => 'Starred', 'type' => 'starred', 'icon' => 'fa-star', 'color' => 'warning', 'sort_order' => 5, 'is_system' => true],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class EmailMessage extends Model
|
||||
{
|
||||
protected $table = 'email_messages';
|
||||
|
||||
protected $fillable = [
|
||||
'email_folder_id', 'message_id', 'subject', 'from_name', 'from_email',
|
||||
'to_name', 'to_email', 'cc', 'bcc', 'body_text', 'body_html',
|
||||
'is_read', 'is_starred', 'is_important', 'is_sent', 'is_draft', 'is_trash',
|
||||
'received_at', 'sent_at', 'imap_uid'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_read' => 'boolean',
|
||||
'is_starred' => 'boolean',
|
||||
'is_important' => 'boolean',
|
||||
'is_sent' => 'boolean',
|
||||
'is_draft' => 'boolean',
|
||||
'is_trash' => 'boolean',
|
||||
'received_at' => 'datetime',
|
||||
'sent_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function folder(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EmailFolder::class, 'email_folder_id');
|
||||
}
|
||||
|
||||
public function attachments(): HasMany
|
||||
{
|
||||
return $this->hasMany(EmailAttachment::class, 'email_message_id');
|
||||
}
|
||||
|
||||
public function getExcerptAttribute(): string
|
||||
{
|
||||
$body = strip_tags($this->body_text ?? $this->body_html ?? '');
|
||||
return mb_strimwidth($body, 0, 100, '...');
|
||||
}
|
||||
|
||||
public function getBodyForDisplayAttribute(): string
|
||||
{
|
||||
if ($this->body_html) {
|
||||
return $this->body_html;
|
||||
}
|
||||
return nl2br(e($this->body_text ?? ''));
|
||||
}
|
||||
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->where('is_read', false);
|
||||
}
|
||||
|
||||
public function scopeInbox($query)
|
||||
{
|
||||
return $query->whereHas('folder', fn($q) => $q->where('type', 'inbox'));
|
||||
}
|
||||
|
||||
public function scopeSent($query)
|
||||
{
|
||||
return $query->where('is_sent', true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class EmailSetting extends Model
|
||||
{
|
||||
protected $table = 'email_settings';
|
||||
|
||||
protected $fillable = [
|
||||
'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password',
|
||||
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
|
||||
'email_address', 'email_name', 'reply_to', 'sync_interval_minutes',
|
||||
'last_sync_at', 'is_active', 'signature', 'signature_enabled'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'signature_enabled' => 'boolean',
|
||||
'last_sync_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function folders(): HasMany
|
||||
{
|
||||
return $this->hasMany(EmailFolder::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function getImapConfig(): array
|
||||
{
|
||||
return [
|
||||
'host' => $this->imap_host,
|
||||
'port' => $this->imap_port,
|
||||
'encryption' => $this->imap_encryption,
|
||||
'username' => $this->imap_username,
|
||||
'password' => $this->imap_password,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDecryptedPassword(): string
|
||||
{
|
||||
if (empty($this->imap_password)) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return Crypt::decryptString($this->imap_password);
|
||||
} catch (\Exception $e) {
|
||||
return $this->imap_password;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDecryptedSmtpPassword(): string
|
||||
{
|
||||
if (empty($this->smtp_password)) {
|
||||
return $this->getDecryptedPassword();
|
||||
}
|
||||
try {
|
||||
return Crypt::decryptString($this->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
return $this->smtp_password;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSmtpConfig(): array
|
||||
{
|
||||
return [
|
||||
'host' => $this->smtp_host ?? $this->imap_host,
|
||||
'port' => $this->smtp_port ?? 587,
|
||||
'encryption' => $this->smtp_encryption ?? 'tls',
|
||||
'username' => $this->smtp_username ?? $this->imap_username,
|
||||
'password' => $this->smtp_password ?? $this->imap_password,
|
||||
];
|
||||
}
|
||||
|
||||
public function getImapClient(): ?\DirectoryTree\ImapEngine\Mailbox
|
||||
{
|
||||
if (!$this->imap_host || !$this->imap_username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$encryption = match ($this->imap_encryption) {
|
||||
'ssl' => 'ssl',
|
||||
'tls' => 'tls',
|
||||
default => 'none',
|
||||
};
|
||||
|
||||
return new \DirectoryTree\ImapEngine\Mailbox([
|
||||
'host' => $this->imap_host,
|
||||
'port' => $this->imap_port ?? 993,
|
||||
'encryption' => $encryption,
|
||||
'validate_cert' => false,
|
||||
'username' => $this->imap_username,
|
||||
'password' => $this->getDecryptedPassword(),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnection(): array
|
||||
{
|
||||
try {
|
||||
$mailbox = $this->getImapClient();
|
||||
|
||||
if (!$mailbox) {
|
||||
return ['success' => false, 'message' => 'Configurazione IMAP non valida'];
|
||||
}
|
||||
|
||||
$mailbox->connect();
|
||||
$mailbox->disconnect();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Connessione IMAP riuscita! Server: ' . $this->imap_host
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (strpos($message, 'Undefined property') !== false) {
|
||||
return ['success' => true, 'message' => 'Connessione IMAP riuscita! (Server: ' . $this->imap_host . ')'];
|
||||
}
|
||||
return ['success' => false, 'message' => $message];
|
||||
}
|
||||
}
|
||||
|
||||
public static function getActive()
|
||||
{
|
||||
return self::where('is_active', true)->first();
|
||||
}
|
||||
|
||||
public function getSignature(): ?string
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Evento extends Model
|
||||
{
|
||||
protected $table = 'eventi';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'nome_evento',
|
||||
'descrizione_evento',
|
||||
'tipo_evento',
|
||||
'tipo_recorrenza',
|
||||
'giorno_settimana',
|
||||
'giorno_mese',
|
||||
'occorrenza_mese',
|
||||
'mesi_recorrenza',
|
||||
'mese_annuale',
|
||||
'ora_inizio',
|
||||
'data_specifica',
|
||||
'durata_minuti',
|
||||
'descrizione',
|
||||
'note',
|
||||
'is_incontro_gruppo',
|
||||
'luogo_indirizzo',
|
||||
'luogo_url_maps',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data_specifica' => 'date',
|
||||
'ora_inizio' => 'datetime:H:i',
|
||||
'is_incontro_gruppo' => 'boolean',
|
||||
];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function gruppi(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Gruppo::class, 'eventi_gruppi')->withTimestamps();
|
||||
}
|
||||
|
||||
public function responsabili(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Individuo::class, 'eventi_responsabili')->withTimestamps();
|
||||
}
|
||||
|
||||
public function documenti(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Documento::class, 'eventi_documenti')->withTimestamps();
|
||||
}
|
||||
|
||||
public function isRicorrente(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza !== null && $this->tipo_recorrenza !== 'singolo';
|
||||
}
|
||||
|
||||
public function isSingolo(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza === 'singolo' || $this->tipo_recorrenza === null;
|
||||
}
|
||||
|
||||
public function isGlobale(): bool
|
||||
{
|
||||
return $this->gruppi()->count() === 0;
|
||||
}
|
||||
|
||||
public function isEventoRicorrenteSettimanale(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza === 'settimanale';
|
||||
}
|
||||
|
||||
public function isEventoRicorrenteMensile(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza === 'mensile';
|
||||
}
|
||||
|
||||
public function isEventoRicorrenteAnnuale(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza === 'annuale';
|
||||
}
|
||||
|
||||
public function isEventoAltro(): bool
|
||||
{
|
||||
return $this->tipo_recorrenza === 'altro';
|
||||
}
|
||||
|
||||
public function isIncroGruppo(): bool
|
||||
{
|
||||
return $this->is_incontro_gruppo === true;
|
||||
}
|
||||
|
||||
public function getGiornoSettimanaLabelAttribute(): string
|
||||
{
|
||||
$giorni = [
|
||||
0 => 'Domenica',
|
||||
1 => 'Lunedì',
|
||||
2 => 'Martedì',
|
||||
3 => 'Mercoledì',
|
||||
4 => 'Giovedì',
|
||||
5 => 'Venerdì',
|
||||
6 => 'Sabato',
|
||||
];
|
||||
return $giorni[$this->giorno_settimana] ?? '-';
|
||||
}
|
||||
|
||||
public function getGiornoSettimanaList(): array
|
||||
{
|
||||
return [
|
||||
0 => ['value' => 0, 'label' => 'Domenica'],
|
||||
1 => ['value' => 1, 'label' => 'Lunedì'],
|
||||
2 => ['value' => 2, 'label' => 'Martedì'],
|
||||
3 => ['value' => 3, 'label' => 'Mercoledì'],
|
||||
4 => ['value' => 4, 'label' => 'Giovedì'],
|
||||
5 => ['value' => 5, 'label' => 'Venerdì'],
|
||||
6 => ['value' => 6, 'label' => 'Sabato'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getOccorrenzeMensiliList(): array
|
||||
{
|
||||
$giorni = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'];
|
||||
$list = [];
|
||||
for ($occ = 1; $occ <= 4; $occ++) {
|
||||
foreach ($giorni as $idx => $giorno) {
|
||||
$value = $occ . ',' . $idx;
|
||||
$label = $occ . '° ' . $giorno;
|
||||
$list[] = ['value' => $value, 'label' => $label];
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function getOccorrenzaMensileLabelAttribute(): string
|
||||
{
|
||||
if (!$this->occorrenza_mese) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$parts = explode(',', $this->occorrenza_mese);
|
||||
if (count($parts) !== 2) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$occorrenza = (int) $parts[0];
|
||||
$giorno = (int) $parts[1];
|
||||
|
||||
$giorni = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'];
|
||||
|
||||
return $occorrenza . '° ' . ($giorni[$giorno] ?? '-');
|
||||
}
|
||||
|
||||
public function getMesiList(): array
|
||||
{
|
||||
return [
|
||||
1 => ['value' => 1, 'label' => 'Gennaio'],
|
||||
2 => ['value' => 2, 'label' => 'Febbraio'],
|
||||
3 => ['value' => 3, 'label' => 'Marzo'],
|
||||
4 => ['value' => 4, 'label' => 'Aprile'],
|
||||
5 => ['value' => 5, 'label' => 'Maggio'],
|
||||
6 => ['value' => 6, 'label' => 'Giugno'],
|
||||
7 => ['value' => 7, 'label' => 'Luglio'],
|
||||
8 => ['value' => 8, 'label' => 'Agosto'],
|
||||
9 => ['value' => 9, 'label' => 'Settembre'],
|
||||
10 => ['value' => 10, 'label' => 'Ottobre'],
|
||||
11 => ['value' => 11, 'label' => 'Novembre'],
|
||||
12 => ['value' => 12, 'label' => 'Dicembre'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getMesiRecorrenzaLabelAttribute(): string
|
||||
{
|
||||
if (!$this->mesi_recorrenza) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$mesiSelezionati = explode(',', $this->mesi_recorrenza);
|
||||
$mesiLabels = $this->getMesiList();
|
||||
|
||||
$labels = array_map(function ($mese) use ($mesiLabels) {
|
||||
return $mesiLabels[$mese]['label'] ?? $mese;
|
||||
}, $mesiSelezionati);
|
||||
|
||||
return implode(', ', $labels);
|
||||
}
|
||||
|
||||
public function getMeseAnnualeLabelAttribute(): string
|
||||
{
|
||||
if (!$this->mese_annuale) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$mesiLabels = $this->getMesiList();
|
||||
return $mesiLabels[$this->mese_annuale]['label'] ?? '-';
|
||||
}
|
||||
|
||||
public function getPeriodicitaLabelAttribute(): string
|
||||
{
|
||||
$labels = [
|
||||
'settimanale' => 'Settimanale',
|
||||
'mensile' => 'Mensile',
|
||||
'annuale' => 'Annuale',
|
||||
'altro' => 'Altro',
|
||||
'singolo' => 'Singolo',
|
||||
];
|
||||
return $labels[$this->tipo_recorrenza] ?? '-';
|
||||
}
|
||||
|
||||
public function getInfoRicorrenzaAttribute(): string
|
||||
{
|
||||
if ($this->tipo_recorrenza === 'settimanale') {
|
||||
return 'Ogni ' . $this->giorno_settimana_label;
|
||||
}
|
||||
|
||||
if ($this->tipo_recorrenza === 'mensile') {
|
||||
$info = [];
|
||||
if ($this->occorrenza_mese) {
|
||||
$info[] = $this->occorrenza_mensile_label;
|
||||
}
|
||||
if ($this->mesi_recorrenza) {
|
||||
$info[] = '(' . $this->mesi_recorrenza_label . ')';
|
||||
}
|
||||
return implode(' ', $info) ?: '-';
|
||||
}
|
||||
|
||||
if ($this->tipo_recorrenza === 'annuale') {
|
||||
if ($this->mese_annuale) {
|
||||
return $this->mese_annuale_label;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->tipo_recorrenza === 'altro') {
|
||||
return $this->giorno_settimana_label ?? '-';
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
public function getInfoIncroAttribute(): ?string
|
||||
{
|
||||
if (!$this->is_incontro_gruppo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = [];
|
||||
|
||||
if ($this->isRicorrente()) {
|
||||
$info[] = $this->info_ricorrenza;
|
||||
} else {
|
||||
$info[] = $this->data_specifica?->format('d/m/Y') ?? '-';
|
||||
}
|
||||
|
||||
if ($this->ora_inizio) {
|
||||
$info[] = 'ore ' . $this->ora_inizio->format('H:i');
|
||||
}
|
||||
|
||||
return implode(' ', $info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Gruppo extends Model
|
||||
{
|
||||
protected $table = 'gruppi';
|
||||
protected $fillable = [
|
||||
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
|
||||
'nome', 'descrizione', 'indirizzo_incontro', 'cap_incontro',
|
||||
'città_incontro', 'sigla_provincia_incontro', 'mappa_posizione', 'metadata'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'responsabile_ids' => 'array',
|
||||
];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Gruppo::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(Gruppo::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function diocesi(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Diocesi::class);
|
||||
}
|
||||
|
||||
public function individui(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Individuo::class, 'gruppo_individuo')
|
||||
->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function getResponsabiliIds(): array
|
||||
{
|
||||
if (empty($this->responsabile_ids)) {
|
||||
return [];
|
||||
}
|
||||
if (is_array($this->responsabile_ids)) {
|
||||
return $this->responsabile_ids;
|
||||
}
|
||||
return json_decode($this->responsabile_ids, true) ?? [];
|
||||
}
|
||||
|
||||
public function getResponsabili(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
$ids = $this->getResponsabiliIds();
|
||||
if (empty($ids)) {
|
||||
return new \Illuminate\Database\Eloquent\Collection();
|
||||
}
|
||||
return Individuo::whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
public function eventi(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Evento::class, 'eventi_gruppi')->withTimestamps();
|
||||
}
|
||||
|
||||
public function documenti(): HasMany
|
||||
{
|
||||
return $this->hasMany(Documento::class, 'visibilita_target_id')
|
||||
->where('visibilita_target_type', self::class);
|
||||
}
|
||||
|
||||
public function getAncestors(): array
|
||||
{
|
||||
$ancestors = [];
|
||||
$current = $this->parent;
|
||||
while ($current) {
|
||||
$ancestors[] = $current;
|
||||
$current = $current->parent;
|
||||
}
|
||||
return $ancestors;
|
||||
}
|
||||
|
||||
public function getDescendants(): array
|
||||
{
|
||||
$descendants = [];
|
||||
foreach ($this->children as $child) {
|
||||
$descendants[] = $child;
|
||||
$descendants = array_merge($descendants, $child->getDescendants());
|
||||
}
|
||||
return $descendants;
|
||||
}
|
||||
|
||||
public function getFullPathAttribute(): string
|
||||
{
|
||||
$path = [$this->nome];
|
||||
foreach (array_reverse($this->getAncestors()) as $ancestor) {
|
||||
$path[] = $ancestor->nome;
|
||||
}
|
||||
return implode(' > ', array_reverse($path));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
class Individuo extends Model
|
||||
{
|
||||
protected $table = 'individui';
|
||||
protected $fillable = [
|
||||
'tenant_id', 'codice_id', 'cognome', 'nome', 'data_nascita',
|
||||
'indirizzo', 'cap', 'città', 'sigla_provincia', 'genere',
|
||||
'tipo_documento', 'numero_documento', 'scadenza_documento', 'note', 'avatar_path'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data_nascita' => 'date',
|
||||
'scadenza_documento' => 'date',
|
||||
];
|
||||
|
||||
public function hasDocumentoScaduto(): bool
|
||||
{
|
||||
return $this->scadenza_documento && $this->scadenza_documento->isPast();
|
||||
}
|
||||
|
||||
public function hasDocumentoScadeEntroGiorni(int $giorni): bool
|
||||
{
|
||||
if (!$this->scadenza_documento || $this->scadenza_documento->isPast()) {
|
||||
return false;
|
||||
}
|
||||
return $this->scadenza_documento->diffInDays(null, true) <= $giorni;
|
||||
}
|
||||
|
||||
public function getGiorniScadenzaDocumentoAttribute(): ?int
|
||||
{
|
||||
if (!$this->scadenza_documento) {
|
||||
return null;
|
||||
}
|
||||
return (int) round($this->scadenza_documento->diffInDays(null, true));
|
||||
}
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($individuo) {
|
||||
if (empty($individuo->codice_id)) {
|
||||
$individuo->codice_id = static::generaCodiceId();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function generaCodiceId(): string
|
||||
{
|
||||
do {
|
||||
$codice = str_pad(random_int(1, 99999), 5, '0', STR_PAD_LEFT);
|
||||
} while (static::where('codice_id', $codice)->exists());
|
||||
|
||||
return $codice;
|
||||
}
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function contatti(): HasMany
|
||||
{
|
||||
return $this->hasMany(Contatto::class)->orderBy('is_primary', 'desc');
|
||||
}
|
||||
|
||||
public function gruppi(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Gruppo::class, 'gruppo_individuo')
|
||||
->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function getRuoloIdsForGruppo($gruppoId): array
|
||||
{
|
||||
$pivot = $this->gruppi()->wherePivot('gruppo_id', $gruppoId)->first()?->pivot;
|
||||
if ($pivot && $pivot->ruolo_ids) {
|
||||
return json_decode($pivot->ruolo_ids, true) ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getRuoliForGruppo($gruppoId): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
$ids = $this->getRuoloIdsForGruppo($gruppoId);
|
||||
return Ruolo::findByIds($ids);
|
||||
}
|
||||
|
||||
public function getRuoliNamesForGruppo($gruppoId): string
|
||||
{
|
||||
return Ruolo::namesByIds($this->getRuoloIdsForGruppo($gruppoId));
|
||||
}
|
||||
|
||||
public function mailingContacts(): HasMany
|
||||
{
|
||||
return $this->hasMany(MailingContact::class);
|
||||
}
|
||||
|
||||
public function eventiResponsabili(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Evento::class, 'eventi_responsabili');
|
||||
}
|
||||
|
||||
public function avatar(): HasOne
|
||||
{
|
||||
return $this->hasOne(Documento::class, 'visibilita_target_id')
|
||||
->where('tipologia', 'avatar')
|
||||
->where('visibilita_target_type', self::class);
|
||||
}
|
||||
|
||||
public function getNomeCompletoAttribute(): string
|
||||
{
|
||||
return $this->cognome . ' ' . $this->nome;
|
||||
}
|
||||
|
||||
public function getEmailPrimariaAttribute(): ?string
|
||||
{
|
||||
return $this->contatti()->where('tipo', 'email')->where('is_primary', true)->first()?->valore
|
||||
?? $this->contatti()->where('tipo', 'email')->first()?->valore;
|
||||
}
|
||||
|
||||
public function getTelefonoPrimarioAttribute(): ?string
|
||||
{
|
||||
return $this->contatti()->whereIn('tipo', ['telefono', 'cellulare'])->where('is_primary', true)->first()?->valore
|
||||
?? $this->contatti()->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore;
|
||||
}
|
||||
|
||||
public function documenti(): HasMany
|
||||
{
|
||||
return $this->hasMany(Documento::class, 'visibilita_target_id')
|
||||
->where('visibilita_target_type', self::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MailingContact extends Model
|
||||
{
|
||||
protected $table = 'mailing_contacts';
|
||||
protected $fillable = ['mailing_list_id', 'individuo_id', 'opt_in', 'opt_in_data', 'opt_out_data'];
|
||||
|
||||
protected $casts = [
|
||||
'opt_in' => 'boolean',
|
||||
'opt_in_data' => 'datetime',
|
||||
'opt_out_data' => 'datetime',
|
||||
];
|
||||
|
||||
public function mailingList(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(MailingList::class);
|
||||
}
|
||||
|
||||
public function individuo(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Individuo::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class MailingList extends Model
|
||||
{
|
||||
protected $table = 'mailing_lists';
|
||||
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva'];
|
||||
|
||||
protected $casts = ['attiva' => 'boolean'];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function contatti(): HasMany
|
||||
{
|
||||
return $this->hasMany(MailingContact::class);
|
||||
}
|
||||
|
||||
public function messaggi(): HasMany
|
||||
{
|
||||
return $this->hasMany(MailingMessaggio::class);
|
||||
}
|
||||
|
||||
public function getIndividui()
|
||||
{
|
||||
return Individuo::whereHas('mailingContacts', function ($q) {
|
||||
$q->where('mailing_list_id', $this->id)->where('opt_in', true);
|
||||
})->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MailingMessaggio extends Model
|
||||
{
|
||||
protected $table = 'mailing_messaggi';
|
||||
protected $fillable = [
|
||||
'tenant_id', 'mailing_list_id', 'user_id', 'oggetto', 'corpo',
|
||||
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'inviato_al' => 'datetime',
|
||||
];
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function mailingList(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(MailingList::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function isInviato(): bool
|
||||
{
|
||||
return $this->stato === 'inviato';
|
||||
}
|
||||
|
||||
public function canBeSent(): bool
|
||||
{
|
||||
return in_array($this->stato, ['bozza', 'fallito']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Notifica extends Model
|
||||
{
|
||||
protected $table = 'notifiche';
|
||||
protected $fillable = ['user_id', 'tipo', 'titolo', 'messaggio', 'link', 'is_read', 'read_at'];
|
||||
|
||||
protected $casts = [
|
||||
'is_read' => 'boolean',
|
||||
'read_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function markAsRead(): void
|
||||
{
|
||||
$this->update(['is_read' => true, 'read_at' => now()]);
|
||||
}
|
||||
|
||||
public function scopeUnread($query)
|
||||
{
|
||||
return $query->where('is_read', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ReportCustom extends Model
|
||||
{
|
||||
protected $table = 'report_custom';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'nome',
|
||||
'descrizione',
|
||||
'tipo_report',
|
||||
'config',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'config' => 'array',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class RolePreset extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'description', 'permissions'];
|
||||
|
||||
protected $casts = [
|
||||
'permissions' => 'array',
|
||||
];
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class, 'role_preset_id');
|
||||
}
|
||||
|
||||
public static function defaultPermissions(): array
|
||||
{
|
||||
return [
|
||||
'individui' => 1,
|
||||
'gruppi' => 1,
|
||||
'eventi' => 1,
|
||||
'documenti' => 1,
|
||||
'mailing' => 1,
|
||||
'viste' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public static function adminPreset(): array
|
||||
{
|
||||
return [
|
||||
'individui' => 2,
|
||||
'gruppi' => 2,
|
||||
'eventi' => 2,
|
||||
'documenti' => 2,
|
||||
'mailing' => 2,
|
||||
'viste' => 2,
|
||||
];
|
||||
}
|
||||
|
||||
public static function operatorePreset(): array
|
||||
{
|
||||
return [
|
||||
'individui' => 2,
|
||||
'gruppi' => 2,
|
||||
'eventi' => 2,
|
||||
'documenti' => 1,
|
||||
'mailing' => 1,
|
||||
'viste' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public static function letturaPreset(): array
|
||||
{
|
||||
return [
|
||||
'individui' => 1,
|
||||
'gruppi' => 1,
|
||||
'eventi' => 1,
|
||||
'documenti' => 1,
|
||||
'mailing' => 1,
|
||||
'viste' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Ruolo extends Model
|
||||
{
|
||||
protected $table = 'ruoli';
|
||||
|
||||
protected $fillable = ['nome', 'descrizione', 'ordine', 'attiva'];
|
||||
|
||||
protected $casts = [
|
||||
'attiva' => 'boolean',
|
||||
'ordine' => 'integer',
|
||||
];
|
||||
|
||||
public static function attive(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return static::where('attiva', true)->orderBy('ordine')->get();
|
||||
}
|
||||
|
||||
public static function opzioni(): array
|
||||
{
|
||||
return static::attive()->pluck('nome', 'id')->toArray();
|
||||
}
|
||||
|
||||
public static function findByIds(?array $ids): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return new \Illuminate\Database\Eloquent\Collection();
|
||||
}
|
||||
return static::whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
public static function namesByIds(?array $ids): string
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return '';
|
||||
}
|
||||
return static::whereIn('id', $ids)
|
||||
->orderBy('ordine')
|
||||
->pluck('nome')
|
||||
->implode(', ');
|
||||
}
|
||||
|
||||
public function getMembriCount(): int
|
||||
{
|
||||
return DB::table('gruppo_individuo')
|
||||
->whereNotNull('ruolo_ids')
|
||||
->whereRaw("JSON_CONTAINS(ruolo_ids, ?)", [json_encode($this->id)])
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Tenant extends Model
|
||||
{
|
||||
protected $table = 'tenants';
|
||||
protected $fillable = ['nome', 'slug', 'email', 'telefono', 'note', 'attivo'];
|
||||
|
||||
public function individui(): HasMany
|
||||
{
|
||||
return $this->hasMany(Individuo::class);
|
||||
}
|
||||
|
||||
public function gruppi(): HasMany
|
||||
{
|
||||
return $this->hasMany(Gruppo::class);
|
||||
}
|
||||
|
||||
public function documenti(): HasMany
|
||||
{
|
||||
return $this->hasMany(Documento::class);
|
||||
}
|
||||
|
||||
public function mailingLists(): HasMany
|
||||
{
|
||||
return $this->hasMany(MailingList::class);
|
||||
}
|
||||
|
||||
public function eventi(): HasMany
|
||||
{
|
||||
return $this->hasMany(Evento::class);
|
||||
}
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class TipologiaDocumento extends Model
|
||||
{
|
||||
protected $table = 'tipologie_documenti';
|
||||
|
||||
protected $fillable = ['nome', 'descrizione', 'ordine', 'attiva'];
|
||||
|
||||
protected $casts = [
|
||||
'attiva' => 'boolean',
|
||||
'ordine' => 'integer',
|
||||
];
|
||||
|
||||
public function documenti(): HasMany
|
||||
{
|
||||
return $this->hasMany(Documento::class, 'tipologia', 'nome');
|
||||
}
|
||||
|
||||
public static function attive(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return static::where('attiva', true)->orderBy('ordine')->get();
|
||||
}
|
||||
|
||||
public static function opzioni(): array
|
||||
{
|
||||
return static::attive()->pluck('nome')->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
protected $fillable = ['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'is_admin' => 'boolean',
|
||||
'permissions' => 'array',
|
||||
'status' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_SUSPENDED = 'suspended';
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const MODULES = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report'];
|
||||
public const LEVEL_NONE = 0;
|
||||
public const LEVEL_READ = 1;
|
||||
public const LEVEL_FULL = 2;
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
public function rolePreset(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(RolePreset::class);
|
||||
}
|
||||
|
||||
public function notifiche(): HasMany
|
||||
{
|
||||
return $this->hasMany(Notifica::class)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function unreadNotifiche(): HasMany
|
||||
{
|
||||
return $this->hasMany(Notifica::class)->where('is_read', false)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function unreadNotificheCount(): int
|
||||
{
|
||||
return $this->notifiche()->unread()->count();
|
||||
}
|
||||
|
||||
public function activityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(ActivityLog::class)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function isSuperAdmin(): bool
|
||||
{
|
||||
return $this->is_admin === true;
|
||||
}
|
||||
|
||||
public static function firstUserIsAdmin(): bool
|
||||
{
|
||||
return self::count() === 1;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->status !== self::STATUS_SUSPENDED;
|
||||
}
|
||||
|
||||
public function getPermission(string $module): int
|
||||
{
|
||||
if ($this->isSuperAdmin()) {
|
||||
return self::LEVEL_FULL;
|
||||
}
|
||||
|
||||
return $this->permissions[$module] ?? self::LEVEL_NONE;
|
||||
}
|
||||
|
||||
public function hasPermission(string $module, int $level = self::LEVEL_READ): bool
|
||||
{
|
||||
return $this->getPermission($module) >= $level;
|
||||
}
|
||||
|
||||
public function canAccess(string $module): bool
|
||||
{
|
||||
return $this->getPermission($module) >= self::LEVEL_READ;
|
||||
}
|
||||
|
||||
public function canManage(string $module): bool
|
||||
{
|
||||
return $this->getPermission($module) >= self::LEVEL_FULL;
|
||||
}
|
||||
|
||||
public function canRead(string $module): bool
|
||||
{
|
||||
return $this->getPermission($module) >= self::LEVEL_READ;
|
||||
}
|
||||
|
||||
public function canWrite(string $module): bool
|
||||
{
|
||||
return $this->getPermission($module) >= self::LEVEL_FULL;
|
||||
}
|
||||
|
||||
public function canDelete(string $module): bool
|
||||
{
|
||||
return $this->getPermission($module) >= self::LEVEL_FULL;
|
||||
}
|
||||
|
||||
public function setPermission(string $module, int $level): self
|
||||
{
|
||||
$permissions = $this->permissions ?? [];
|
||||
$permissions[$module] = $level;
|
||||
$this->permissions = $permissions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPermissions(array $permissions): self
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPermissionsSummary(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::MODULES as $module) {
|
||||
$level = $this->getPermission($module);
|
||||
$result[$module] = match ($level) {
|
||||
self::LEVEL_NONE => 'Nessuno',
|
||||
self::LEVEL_READ => 'Lettura',
|
||||
self::LEVEL_FULL => 'Completo',
|
||||
};
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function applyRolePreset(RolePreset $preset): self
|
||||
{
|
||||
$this->role_preset_id = $preset->id;
|
||||
$this->permissions = $preset->permissions;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class VistaReport extends Model
|
||||
{
|
||||
protected $table = 'viste_report';
|
||||
protected $fillable = [
|
||||
'user_id', 'nome', 'tipo', 'colonne_visibili',
|
||||
'colonne_ordinamento', 'filtri', 'ricerca'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'colonne_visibili' => 'array',
|
||||
'colonne_ordinamento' => 'array',
|
||||
'filtri' => 'array',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use App\Models\Individuo;
|
||||
use App\Models\Gruppo;
|
||||
use App\Models\Evento;
|
||||
use App\Models\MailingList;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
\Illuminate\Pagination\Paginator::defaultView('vendor.pagination.simple-bootstrap-4');
|
||||
\Illuminate\Pagination\LengthAwarePaginator::defaultView('vendor.pagination.simple-bootstrap-4');
|
||||
|
||||
Relation::morphMap([
|
||||
'individuo' => Individuo::class,
|
||||
'gruppo' => Gruppo::class,
|
||||
'evento' => Evento::class,
|
||||
'mailing' => MailingList::class,
|
||||
'associazione' => \App\Models\Associazione::class,
|
||||
'federazione' => \App\Models\Federazione::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $policies = [
|
||||
//
|
||||
];
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
Gate::before(function ($user, $ability) {
|
||||
if ($user->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
foreach (Permission::where('guard_name', 'web')->get() as $permission) {
|
||||
Gate::define($permission->name, function ($user) use ($permission) {
|
||||
return $user->hasPermissionTo($permission);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user