glastree_on_gitea

This commit is contained in:
2026-05-26 08:14:29 +02:00
commit 0bed099d05
9556 changed files with 1186307 additions and 0 deletions
+83
View File
@@ -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');
}
}
+125
View File
@@ -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();
}
}
+19
View File
@@ -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();
}
}
+21
View File
@@ -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);
}
}
+17
View File
@@ -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);
}
}
+61
View File
@@ -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;
}
}
+80
View File
@@ -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';
}
}
+39
View 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],
];
}
}
+69
View File
@@ -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);
}
}
+137
View File
@@ -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;
}
}
+267
View File
@@ -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);
}
}
+111
View File
@@ -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));
}
}
+142
View File
@@ -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);
}
}
+28
View File
@@ -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);
}
}
+42
View File
@@ -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();
}
}
+44
View File
@@ -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']);
}
}
+32
View File
@@ -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);
}
}
+28
View File
@@ -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);
}
}
+68
View File
@@ -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,
];
}
}
+55
View File
@@ -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();
}
}
+42
View File
@@ -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);
}
}
+33
View File
@@ -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();
}
}
+159
View File
@@ -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;
}
}
+26
View File
@@ -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);
}
}