328 lines
8.4 KiB
PHP
328 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|
use Carbon\Carbon;
|
|
|
|
class Documento extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* NETGESCON MODEL: Documento
|
|
*
|
|
* Model per la gestione avanzata dei documenti condominiali
|
|
* con supporto OCR, tagging e integrazione contabile.
|
|
*
|
|
* @author GitHub Copilot
|
|
* @date 21/07/2025
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
protected $table = 'documenti';
|
|
|
|
protected $fillable = [
|
|
// Campi legacy per compatibilità
|
|
'documentable_id',
|
|
'documentable_type',
|
|
'nome_file',
|
|
'path_file',
|
|
'tipo_documento',
|
|
'mime_type',
|
|
'descrizione',
|
|
'xml_data',
|
|
'hash_file',
|
|
|
|
// Nuovi campi per sistema avanzato
|
|
'stabile_id',
|
|
'unita_id',
|
|
'utente_id',
|
|
'nome',
|
|
'tipologia',
|
|
'fornitore',
|
|
'data_documento',
|
|
'data_scadenza',
|
|
'importo_collegato',
|
|
'categoria_spesa',
|
|
'tags',
|
|
'note',
|
|
'numero_protocollo',
|
|
'percorso_file',
|
|
'numero_pagine',
|
|
'dimensione_file',
|
|
'estensione',
|
|
'contenuto_ocr',
|
|
'metadati_ocr',
|
|
'approvato',
|
|
'archiviato',
|
|
'urgente',
|
|
'is_demo',
|
|
'movimento_contabile_id',
|
|
'collegato_budget',
|
|
'data_upload',
|
|
'data_approvazione',
|
|
'data_archiviazione',
|
|
'approvato_da'
|
|
];
|
|
|
|
protected $casts = [
|
|
'xml_data' => 'array',
|
|
'metadati_ocr' => 'array',
|
|
'dimensione_file' => 'integer',
|
|
'data_documento' => 'date',
|
|
'data_scadenza' => 'date',
|
|
'data_upload' => 'datetime',
|
|
'data_approvazione' => 'datetime',
|
|
'data_archiviazione' => 'datetime',
|
|
'importo_collegato' => 'decimal:2',
|
|
'approvato' => 'boolean',
|
|
'archiviato' => 'boolean',
|
|
'urgente' => 'boolean',
|
|
'is_demo' => 'boolean',
|
|
'collegato_budget' => 'boolean',
|
|
'created_at' => 'datetime',
|
|
'updated_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Relazioni Eloquent
|
|
*/
|
|
public function documentable(): MorphTo
|
|
{
|
|
return $this->morphTo();
|
|
}
|
|
|
|
public function stabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
public function unita(): BelongsTo
|
|
{
|
|
return $this->belongsTo(UnitaImmobiliare::class);
|
|
}
|
|
|
|
public function utente(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function approvatoDa(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'approvato_da');
|
|
}
|
|
|
|
/**
|
|
* Scopes per query comuni
|
|
*/
|
|
public function scopeTipo($query, $tipo)
|
|
{
|
|
return $query->where('tipo_documento', $tipo);
|
|
}
|
|
|
|
public function scopeUrgenti($query)
|
|
{
|
|
return $query->where('urgente', true);
|
|
}
|
|
|
|
public function scopeNonApprovati($query)
|
|
{
|
|
return $query->where('approvato', false);
|
|
}
|
|
|
|
public function scopeInScadenza($query, $giorni = 30)
|
|
{
|
|
return $query->whereNotNull('data_scadenza')
|
|
->where('data_scadenza', '<=', Carbon::now()->addDays($giorni));
|
|
}
|
|
|
|
public function scopePerTipologia($query, $tipologia)
|
|
{
|
|
return $query->where('tipologia', $tipologia);
|
|
}
|
|
|
|
public function scopePerCategoria($query, $categoria)
|
|
{
|
|
return $query->where('categoria_spesa', $categoria);
|
|
}
|
|
|
|
public function scopeDemo($query)
|
|
{
|
|
return $query->where('is_demo', true);
|
|
}
|
|
|
|
public function scopeReali($query)
|
|
{
|
|
return $query->where('is_demo', false);
|
|
}
|
|
|
|
/**
|
|
* Accessor e Mutators
|
|
*/
|
|
public function getTagsArrayAttribute()
|
|
{
|
|
return $this->tags ? explode(',', $this->tags) : [];
|
|
}
|
|
|
|
public function setTagsArrayAttribute($value)
|
|
{
|
|
$this->attributes['tags'] = is_array($value) ? implode(',', $value) : $value;
|
|
}
|
|
|
|
public function getDimensioneFormattataAttribute()
|
|
{
|
|
if (!$this->dimensione_file) return null;
|
|
|
|
$size = $this->dimensione_file;
|
|
$units = ['B', 'KB', 'MB', 'GB'];
|
|
|
|
for ($i = 0; $size > 1024 && $i < count($units) - 1; $i++) {
|
|
$size /= 1024;
|
|
}
|
|
|
|
return round($size, 2) . ' ' . $units[$i];
|
|
}
|
|
|
|
public function getGiorniAllaScadenzaAttribute()
|
|
{
|
|
if (!$this->data_scadenza) return null;
|
|
|
|
return Carbon::now()->diffInDays($this->data_scadenza, false);
|
|
}
|
|
|
|
public function getStatoScadenzaAttribute()
|
|
{
|
|
$giorni = $this->giorni_alla_scadenza;
|
|
|
|
if ($giorni === null) return 'nessuna_scadenza';
|
|
if ($giorni < 0) return 'scaduto';
|
|
if ($giorni <= 7) return 'scadenza_imminente';
|
|
if ($giorni <= 30) return 'in_scadenza';
|
|
|
|
return 'valido';
|
|
}
|
|
|
|
/**
|
|
* Metodi di utilità
|
|
*/
|
|
public function generaNumeroProtocollo()
|
|
{
|
|
$prefisso = strtoupper(substr($this->tipologia ?? 'DOC', 0, 4));
|
|
$anno = Carbon::now()->year;
|
|
|
|
// Trova il prossimo numero progressivo per l'anno
|
|
$ultimoNumero = static::where('numero_protocollo', 'like', "{$prefisso}-{$anno}-%")
|
|
->orderBy('numero_protocollo', 'desc')
|
|
->first();
|
|
|
|
if ($ultimoNumero && preg_match("/{$prefisso}-{$anno}-(\d+)/", $ultimoNumero->numero_protocollo, $matches)) {
|
|
$progressivo = intval($matches[1]) + 1;
|
|
} else {
|
|
$progressivo = 1;
|
|
}
|
|
|
|
return "{$prefisso}-{$anno}-" . str_pad($progressivo, 3, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
public function hasTag($tag)
|
|
{
|
|
return in_array($tag, $this->tags_array);
|
|
}
|
|
|
|
public function addTag($tag)
|
|
{
|
|
$tags = $this->tags_array;
|
|
if (!in_array($tag, $tags)) {
|
|
$tags[] = $tag;
|
|
$this->tags_array = $tags;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function removeTag($tag)
|
|
{
|
|
$tags = $this->tags_array;
|
|
$index = array_search($tag, $tags);
|
|
if ($index !== false) {
|
|
unset($tags[$index]);
|
|
$this->tags_array = array_values($tags);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function approva($userId = null)
|
|
{
|
|
$this->approvato = true;
|
|
$this->data_approvazione = Carbon::now();
|
|
if ($userId) {
|
|
$this->approvato_da = $userId;
|
|
}
|
|
return $this->save();
|
|
}
|
|
|
|
public function archivia()
|
|
{
|
|
$this->archiviato = true;
|
|
$this->data_archiviazione = Carbon::now();
|
|
return $this->save();
|
|
}
|
|
|
|
/**
|
|
* Ricerca full-text (compatibile con entrambi i sistemi)
|
|
*/
|
|
public function scopeRicerca($query, $termine)
|
|
{
|
|
return $query->where(function ($q) use ($termine) {
|
|
$q->where('nome', 'like', "%{$termine}%")
|
|
->orWhere('nome_file', 'like', "%{$termine}%")
|
|
->orWhere('fornitore', 'like', "%{$termine}%")
|
|
->orWhere('note', 'like', "%{$termine}%")
|
|
->orWhere('descrizione', 'like', "%{$termine}%")
|
|
->orWhere('contenuto_ocr', 'like', "%{$termine}%");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Statistiche
|
|
*/
|
|
public static function statistichePerStabile($stabileId)
|
|
{
|
|
return static::where('stabile_id', $stabileId)
|
|
->selectRaw('
|
|
COUNT(*) as totale_documenti,
|
|
COUNT(CASE WHEN approvato = 1 THEN 1 END) as approvati,
|
|
COUNT(CASE WHEN urgente = 1 THEN 1 END) as urgenti,
|
|
COUNT(CASE WHEN data_scadenza IS NOT NULL AND data_scadenza <= DATE_ADD(NOW(), INTERVAL 30 DAY) THEN 1 END) as in_scadenza,
|
|
SUM(CASE WHEN importo_collegato IS NOT NULL THEN importo_collegato ELSE 0 END) as valore_totale
|
|
')
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Accessor per URL download
|
|
*/
|
|
public function getUrlDownloadAttribute()
|
|
{
|
|
return route('admin.documenti.download', $this->id);
|
|
}
|
|
|
|
/**
|
|
* Accessor per dimensione leggibile
|
|
*/
|
|
public function getDimensioneLeggibileAttribute()
|
|
{
|
|
$bytes = $this->dimensione_file;
|
|
$units = ['B', 'KB', 'MB', 'GB'];
|
|
|
|
for ($i = 0; $bytes > 1024; $i++) {
|
|
$bytes /= 1024;
|
|
}
|
|
|
|
return round($bytes, 2) . ' ' . $units[$i];
|
|
}
|
|
}
|