📋 Commit iniziale con: - ✅ Documentazione unificata in docs/ - ✅ Codice Laravel in netgescon-laravel/ - ✅ Script automazione in scripts/ - ✅ Configurazione sync rsync - ✅ Struttura organizzata e pulita 🔄 Versione: 2025.07.19-1644 🎯 Sistema pronto per Git distribuito
767 lines
24 KiB
Markdown
767 lines
24 KiB
Markdown
# 📁 **NetGesCon Laravel - Sistema Gestione Documentale**
|
|
|
|
## 📍 **OVERVIEW GENERALE**
|
|
Sistema integrato per gestione documenti condominiali con supporto per archiviazione locale, cloud (Office 365, Google Drive) e audit documentale completo.
|
|
|
|
---
|
|
|
|
## 🏗️ **ARCHITETTURA ARCHIVIAZIONE DOCUMENTI**
|
|
|
|
### **1. Struttura Database Documenti**
|
|
|
|
#### **A. Tabella Principale Documenti**
|
|
```sql
|
|
-- Migration: create_documenti_table.php
|
|
CREATE TABLE documenti (
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|
stabile_id BIGINT UNSIGNED NOT NULL,
|
|
amministratore_id BIGINT UNSIGNED NOT NULL,
|
|
categoria_documento ENUM('contratto', 'fattura', 'verbale', 'corrispondenza', 'tecnico', 'legale', 'assicurazione', 'altro') NOT NULL,
|
|
tipo_documento VARCHAR(100) NOT NULL,
|
|
titolo VARCHAR(255) NOT NULL,
|
|
descrizione TEXT,
|
|
nome_file VARCHAR(255) NOT NULL,
|
|
nome_file_originale VARCHAR(255) NOT NULL,
|
|
path_relativo VARCHAR(500) NOT NULL,
|
|
path_assoluto VARCHAR(1000) NOT NULL,
|
|
dimensione_file BIGINT UNSIGNED NOT NULL,
|
|
mime_type VARCHAR(100) NOT NULL,
|
|
hash_file VARCHAR(64) NOT NULL, -- SHA256 per integrità
|
|
stato_documento ENUM('bozza', 'attivo', 'archiviato', 'eliminato') DEFAULT 'attivo',
|
|
data_documento DATE,
|
|
data_scadenza DATE NULL,
|
|
numero_protocollo VARCHAR(50) NULL,
|
|
anno_protocollo YEAR NULL,
|
|
note_interne TEXT,
|
|
metadati_personalizzati JSON,
|
|
visibilita ENUM('privato', 'amministratore', 'condomini', 'pubblico') DEFAULT 'amministratore',
|
|
-- Audit fields
|
|
caricato_da BIGINT UNSIGNED NOT NULL,
|
|
modificato_da BIGINT UNSIGNED NULL,
|
|
verificato_da BIGINT UNSIGNED NULL,
|
|
verificato_at TIMESTAMP NULL,
|
|
-- Cloud sync
|
|
sincronizzato_cloud BOOLEAN DEFAULT FALSE,
|
|
cloud_provider ENUM('office365', 'google_drive', 'dropbox') NULL,
|
|
cloud_file_id VARCHAR(255) NULL,
|
|
cloud_ultimo_sync TIMESTAMP NULL,
|
|
-- Timestamps
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP NULL,
|
|
|
|
-- Indexes
|
|
INDEX idx_stabile_categoria (stabile_id, categoria_documento),
|
|
INDEX idx_data_documento (data_documento),
|
|
INDEX idx_data_scadenza (data_scadenza),
|
|
INDEX idx_stato (stato_documento),
|
|
INDEX idx_hash (hash_file),
|
|
INDEX idx_protocollo (numero_protocollo, anno_protocollo),
|
|
|
|
-- Foreign Keys
|
|
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (amministratore_id) REFERENCES amministratori(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (caricato_da) REFERENCES users(id),
|
|
FOREIGN KEY (modificato_da) REFERENCES users(id),
|
|
FOREIGN KEY (verificato_da) REFERENCES users(id)
|
|
);
|
|
```
|
|
|
|
#### **B. Tabella Collegamenti Documenti**
|
|
```sql
|
|
-- Per collegare documenti a voci di spesa, ripartizioni, etc.
|
|
CREATE TABLE collegamenti_documenti (
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|
documento_id BIGINT UNSIGNED NOT NULL,
|
|
entita_tipo VARCHAR(50) NOT NULL, -- 'voce_spesa', 'ripartizione_spese', 'rata', etc.
|
|
entita_id BIGINT UNSIGNED NOT NULL,
|
|
tipo_collegamento ENUM('supporto', 'fattura', 'ricevuta', 'autorizzazione', 'altro') NOT NULL,
|
|
note VARCHAR(255),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_documento (documento_id),
|
|
INDEX idx_entita (entita_tipo, entita_id),
|
|
|
|
FOREIGN KEY (documento_id) REFERENCES documenti(id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
#### **C. Tabella Versioni Documenti**
|
|
```sql
|
|
CREATE TABLE versioni_documenti (
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|
documento_id BIGINT UNSIGNED NOT NULL,
|
|
numero_versione INT NOT NULL DEFAULT 1,
|
|
nome_file VARCHAR(255) NOT NULL,
|
|
path_relativo VARCHAR(500) NOT NULL,
|
|
dimensione_file BIGINT UNSIGNED NOT NULL,
|
|
hash_file VARCHAR(64) NOT NULL,
|
|
modifiche_descrizione TEXT,
|
|
creato_da BIGINT UNSIGNED NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
INDEX idx_documento_versione (documento_id, numero_versione),
|
|
|
|
FOREIGN KEY (documento_id) REFERENCES documenti(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (creato_da) REFERENCES users(id)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 🗂️ **STRUTTURA CARTELLE FISICHE**
|
|
|
|
### **1. Organizzazione Filesystem**
|
|
```
|
|
storage/app/
|
|
├── documenti/
|
|
│ ├── amministratore_{id}/
|
|
│ │ ├── stabile_{id}/
|
|
│ │ │ ├── {anno}/
|
|
│ │ │ │ ├── contratti/
|
|
│ │ │ │ ├── fatture/
|
|
│ │ │ │ │ ├── {mese}/
|
|
│ │ │ │ │ │ ├── fornitori/
|
|
│ │ │ │ │ │ └── utenze/
|
|
│ │ │ │ ├── verbali/
|
|
│ │ │ │ │ ├── assemblee/
|
|
│ │ │ │ │ └── consiglio/
|
|
│ │ │ │ ├── corrispondenza/
|
|
│ │ │ │ │ ├── ingresso/
|
|
│ │ │ │ │ └── uscita/
|
|
│ │ │ │ ├── tecnici/
|
|
│ │ │ │ │ ├── progetti/
|
|
│ │ │ │ │ ├── certificati/
|
|
│ │ │ │ │ └── collaudi/
|
|
│ │ │ │ ├── legali/
|
|
│ │ │ │ ├── assicurazioni/
|
|
│ │ │ │ └── altro/
|
|
│ │ │ └── versioni/
|
|
│ │ │ └── {documento_id}/
|
|
│ │ └── backup/
|
|
│ │ └── {data}/
|
|
│ └── templates/
|
|
│ ├── contratti/
|
|
│ ├── lettere/
|
|
│ └── verbali/
|
|
```
|
|
|
|
### **2. Naming Convention**
|
|
```
|
|
Formato: {YYYY}{MM}{DD}_{categoria}_{progressivo}_{descrizione_breve}.{ext}
|
|
Esempio: 20250127_fattura_001_enel_energia_elettrica.pdf
|
|
20250127_verbale_001_assemblea_ordinaria.pdf
|
|
20250127_contratto_001_pulizie_ditta_abc.pdf
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 **MODELLO ELOQUENT E SERVIZI**
|
|
|
|
### **1. Modello Documento**
|
|
```php
|
|
// app/Models/Documento.php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class Documento extends Model
|
|
{
|
|
use SoftDeletes;
|
|
|
|
protected $table = 'documenti';
|
|
|
|
protected $fillable = [
|
|
'stabile_id', 'amministratore_id', 'categoria_documento',
|
|
'tipo_documento', 'titolo', 'descrizione', 'nome_file',
|
|
'nome_file_originale', 'path_relativo', 'dimensione_file',
|
|
'mime_type', 'hash_file', 'stato_documento', 'data_documento',
|
|
'data_scadenza', 'numero_protocollo', 'anno_protocollo',
|
|
'note_interne', 'metadati_personalizzati', 'visibilita',
|
|
'caricato_da'
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_documento' => 'date',
|
|
'data_scadenza' => 'date',
|
|
'metadati_personalizzati' => 'array',
|
|
'sincronizzato_cloud' => 'boolean',
|
|
'verificato_at' => 'datetime',
|
|
'cloud_ultimo_sync' => 'datetime'
|
|
];
|
|
|
|
// Relazioni
|
|
public function stabile()
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
public function amministratore()
|
|
{
|
|
return $this->belongsTo(Amministratore::class);
|
|
}
|
|
|
|
public function caricatoDa()
|
|
{
|
|
return $this->belongsTo(User::class, 'caricato_da');
|
|
}
|
|
|
|
public function modificatoDa()
|
|
{
|
|
return $this->belongsTo(User::class, 'modificato_da');
|
|
}
|
|
|
|
public function verificatoDa()
|
|
{
|
|
return $this->belongsTo(User::class, 'verificato_da');
|
|
}
|
|
|
|
public function versioni()
|
|
{
|
|
return $this->hasMany(VersioneDocumento::class);
|
|
}
|
|
|
|
public function collegamenti()
|
|
{
|
|
return $this->hasMany(CollegamentoDocumento::class);
|
|
}
|
|
|
|
// Scopes
|
|
public function scopePerStabile($query, $stabileId)
|
|
{
|
|
return $query->where('stabile_id', $stabileId);
|
|
}
|
|
|
|
public function scopePerCategoria($query, $categoria)
|
|
{
|
|
return $query->where('categoria_documento', $categoria);
|
|
}
|
|
|
|
public function scopeInScadenza($query, $giorni = 30)
|
|
{
|
|
return $query->whereNotNull('data_scadenza')
|
|
->whereBetween('data_scadenza', [
|
|
now()->toDateString(),
|
|
now()->addDays($giorni)->toDateString()
|
|
]);
|
|
}
|
|
|
|
public function scopeAttivi($query)
|
|
{
|
|
return $query->where('stato_documento', 'attivo');
|
|
}
|
|
|
|
// Metodi utilità
|
|
public function getPathCompletoAttribute()
|
|
{
|
|
return Storage::path($this->path_relativo);
|
|
}
|
|
|
|
public function getUrlDownloadAttribute()
|
|
{
|
|
return route('documenti.download', $this->id);
|
|
}
|
|
|
|
public function getDimensioneFormattataAttribute()
|
|
{
|
|
return $this->formatBytes($this->dimensione_file);
|
|
}
|
|
|
|
private function formatBytes($size)
|
|
{
|
|
$units = ['B', 'KB', 'MB', 'GB'];
|
|
$i = 0;
|
|
while ($size >= 1024 && $i < count($units) - 1) {
|
|
$size /= 1024;
|
|
$i++;
|
|
}
|
|
return round($size, 2) . ' ' . $units[$i];
|
|
}
|
|
|
|
public function verificaIntegrita()
|
|
{
|
|
if (!Storage::exists($this->path_relativo)) {
|
|
return false;
|
|
}
|
|
|
|
$hashCorrente = hash_file('sha256', Storage::path($this->path_relativo));
|
|
return $hashCorrente === $this->hash_file;
|
|
}
|
|
|
|
public function creaVersione($motivo = null)
|
|
{
|
|
return VersioneDocumento::create([
|
|
'documento_id' => $this->id,
|
|
'numero_versione' => $this->versioni()->max('numero_versione') + 1,
|
|
'nome_file' => $this->nome_file,
|
|
'path_relativo' => $this->path_relativo,
|
|
'dimensione_file' => $this->dimensione_file,
|
|
'hash_file' => $this->hash_file,
|
|
'modifiche_descrizione' => $motivo,
|
|
'creato_da' => auth()->id()
|
|
]);
|
|
}
|
|
|
|
// Categorie statiche
|
|
public static function getCategorie()
|
|
{
|
|
return [
|
|
'contratto' => 'Contratti',
|
|
'fattura' => 'Fatture',
|
|
'verbale' => 'Verbali',
|
|
'corrispondenza' => 'Corrispondenza',
|
|
'tecnico' => 'Documenti Tecnici',
|
|
'legale' => 'Documenti Legali',
|
|
'assicurazione' => 'Assicurazioni',
|
|
'altro' => 'Altro'
|
|
];
|
|
}
|
|
|
|
public static function getTipiVisibilita()
|
|
{
|
|
return [
|
|
'privato' => 'Solo Amministratore',
|
|
'amministratore' => 'Staff Amministrazione',
|
|
'condomini' => 'Condomini',
|
|
'pubblico' => 'Pubblico'
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Servizio Gestione Documenti**
|
|
```php
|
|
// app/Services/DocumentoService.php
|
|
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Documento;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class DocumentoService
|
|
{
|
|
public function caricaDocumento(UploadedFile $file, array $dati)
|
|
{
|
|
// Validazione file
|
|
$this->validaFile($file);
|
|
|
|
// Genera path di destinazione
|
|
$pathRelativo = $this->generaPath($dati);
|
|
|
|
// Calcola hash per integrità
|
|
$hashFile = hash_file('sha256', $file->getPathname());
|
|
|
|
// Verifica duplicati
|
|
if ($this->verificaDuplicato($hashFile, $dati['stabile_id'])) {
|
|
throw new \Exception('Il documento è già presente nell\'archivio');
|
|
}
|
|
|
|
// Genera nome file univoco
|
|
$nomeFile = $this->generaNomeFile($file, $dati);
|
|
$pathCompleto = $pathRelativo . '/' . $nomeFile;
|
|
|
|
// Salva il file
|
|
$file->storeAs(dirname($pathCompleto), basename($pathCompleto));
|
|
|
|
// Crea record in database
|
|
return Documento::create([
|
|
'stabile_id' => $dati['stabile_id'],
|
|
'amministratore_id' => $dati['amministratore_id'],
|
|
'categoria_documento' => $dati['categoria_documento'],
|
|
'tipo_documento' => $dati['tipo_documento'],
|
|
'titolo' => $dati['titolo'],
|
|
'descrizione' => $dati['descrizione'] ?? null,
|
|
'nome_file' => $nomeFile,
|
|
'nome_file_originale' => $file->getClientOriginalName(),
|
|
'path_relativo' => $pathCompleto,
|
|
'path_assoluto' => Storage::path($pathCompleto),
|
|
'dimensione_file' => $file->getSize(),
|
|
'mime_type' => $file->getMimeType(),
|
|
'hash_file' => $hashFile,
|
|
'data_documento' => $dati['data_documento'] ?? now()->toDateString(),
|
|
'data_scadenza' => $dati['data_scadenza'] ?? null,
|
|
'numero_protocollo' => $dati['numero_protocollo'] ?? null,
|
|
'anno_protocollo' => $dati['anno_protocollo'] ?? now()->year,
|
|
'note_interne' => $dati['note_interne'] ?? null,
|
|
'metadati_personalizzati' => $dati['metadati'] ?? [],
|
|
'visibilita' => $dati['visibilita'] ?? 'amministratore',
|
|
'caricato_da' => auth()->id()
|
|
]);
|
|
}
|
|
|
|
private function generaPath(array $dati)
|
|
{
|
|
$anno = $dati['anno'] ?? now()->year;
|
|
$mese = $dati['mese'] ?? now()->format('m');
|
|
|
|
return "documenti/amministratore_{$dati['amministratore_id']}/stabile_{$dati['stabile_id']}/{$anno}/{$dati['categoria_documento']}" .
|
|
($dati['categoria_documento'] === 'fattura' ? "/{$mese}" : '');
|
|
}
|
|
|
|
private function generaNomeFile(UploadedFile $file, array $dati)
|
|
{
|
|
$data = now()->format('Ymd');
|
|
$categoria = $dati['categoria_documento'];
|
|
$progressivo = $this->getProgressivo($dati['stabile_id'], $categoria);
|
|
$descrizione = Str::slug($dati['descrizione_breve'] ?? 'documento');
|
|
$estensione = $file->getClientOriginalExtension();
|
|
|
|
return "{$data}_{$categoria}_{$progressivo}_{$descrizione}.{$estensione}";
|
|
}
|
|
|
|
private function getProgressivo($stabileId, $categoria)
|
|
{
|
|
$ultimo = Documento::where('stabile_id', $stabileId)
|
|
->where('categoria_documento', $categoria)
|
|
->whereDate('created_at', now()->toDateString())
|
|
->count();
|
|
|
|
return str_pad($ultimo + 1, 3, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
private function validaFile(UploadedFile $file)
|
|
{
|
|
$tipiConsentiti = [
|
|
'application/pdf',
|
|
'image/jpeg',
|
|
'image/png',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'application/vnd.ms-excel',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
];
|
|
|
|
if (!in_array($file->getMimeType(), $tipiConsentiti)) {
|
|
throw new \Exception('Tipo di file non consentito');
|
|
}
|
|
|
|
if ($file->getSize() > 50 * 1024 * 1024) { // 50MB
|
|
throw new \Exception('File troppo grande (max 50MB)');
|
|
}
|
|
}
|
|
|
|
private function verificaDuplicato($hash, $stabileId)
|
|
{
|
|
return Documento::where('hash_file', $hash)
|
|
->where('stabile_id', $stabileId)
|
|
->where('stato_documento', 'attivo')
|
|
->exists();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ☁️ **INTEGRAZIONE CLOUD STORAGE**
|
|
|
|
### **1. Servizio Office 365**
|
|
```php
|
|
// app/Services/Office365Service.php
|
|
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Microsoft\Graph\Graph;
|
|
use Microsoft\Graph\Model;
|
|
|
|
class Office365Service
|
|
{
|
|
private $graph;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->graph = new Graph();
|
|
$this->graph->setAccessToken($this->getAccessToken());
|
|
}
|
|
|
|
public function sincronizzaDocumento(Documento $documento)
|
|
{
|
|
try {
|
|
// Crea cartella se non esiste
|
|
$cartellaPadre = $this->creaCatellaStabile($documento->stabile);
|
|
|
|
// Upload del file
|
|
$fileContent = Storage::get($documento->path_relativo);
|
|
$uploadedFile = $this->graph->createRequest('PUT',
|
|
"/me/drive/items/{$cartellaPadre}/children/{$documento->nome_file}/content")
|
|
->attachBody($fileContent)
|
|
->execute();
|
|
|
|
// Aggiorna documento con info cloud
|
|
$documento->update([
|
|
'sincronizzato_cloud' => true,
|
|
'cloud_provider' => 'office365',
|
|
'cloud_file_id' => $uploadedFile->getId(),
|
|
'cloud_ultimo_sync' => now()
|
|
]);
|
|
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
\Log::error('Errore sincronizzazione Office 365: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function creaCatellaStabile($stabile)
|
|
{
|
|
$nomeCartella = "Stabile_{$stabile->codice}_{$stabile->denominazione}";
|
|
|
|
// Verifica se esiste
|
|
$folders = $this->graph->createRequest('GET',
|
|
"/me/drive/root/children?filter=name eq '{$nomeCartella}'")
|
|
->execute();
|
|
|
|
if ($folders->getBody()['value']) {
|
|
return $folders->getBody()['value'][0]['id'];
|
|
}
|
|
|
|
// Crea nuova cartella
|
|
$folderData = [
|
|
'name' => $nomeCartella,
|
|
'folder' => new \stdClass()
|
|
];
|
|
|
|
$newFolder = $this->graph->createRequest('POST', '/me/drive/root/children')
|
|
->attachBody($folderData)
|
|
->execute();
|
|
|
|
return $newFolder->getId();
|
|
}
|
|
|
|
private function getAccessToken()
|
|
{
|
|
// Implementa OAuth2 flow per Office 365
|
|
// Restituisce access token valido
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Servizio Google Drive**
|
|
```php
|
|
// app/Services/GoogleDriveService.php
|
|
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Google_Client;
|
|
use Google_Service_Drive;
|
|
use Google_Service_Drive_DriveFile;
|
|
|
|
class GoogleDriveService
|
|
{
|
|
private $client;
|
|
private $service;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->client = new Google_Client();
|
|
$this->client->setClientId(config('services.google.client_id'));
|
|
$this->client->setClientSecret(config('services.google.client_secret'));
|
|
$this->client->setRedirectUri(config('services.google.redirect_uri'));
|
|
$this->client->addScope(Google_Service_Drive::DRIVE);
|
|
|
|
$this->service = new Google_Service_Drive($this->client);
|
|
}
|
|
|
|
public function sincronizzaDocumento(Documento $documento)
|
|
{
|
|
try {
|
|
// Crea cartella se non esiste
|
|
$cartellaPadre = $this->creaCatellaStabile($documento->stabile);
|
|
|
|
// Prepara file per upload
|
|
$fileMetadata = new Google_Service_Drive_DriveFile([
|
|
'name' => $documento->nome_file,
|
|
'parents' => [$cartellaPadre]
|
|
]);
|
|
|
|
$content = Storage::get($documento->path_relativo);
|
|
|
|
$file = $this->service->files->create($fileMetadata, [
|
|
'data' => $content,
|
|
'mimeType' => $documento->mime_type,
|
|
'uploadType' => 'multipart'
|
|
]);
|
|
|
|
// Aggiorna documento
|
|
$documento->update([
|
|
'sincronizzato_cloud' => true,
|
|
'cloud_provider' => 'google_drive',
|
|
'cloud_file_id' => $file->id,
|
|
'cloud_ultimo_sync' => now()
|
|
]);
|
|
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
\Log::error('Errore sincronizzazione Google Drive: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function creaCatellaStabile($stabile)
|
|
{
|
|
$nomeCartella = "Stabile_{$stabile->codice}_{$stabile->denominazione}";
|
|
|
|
// Cerca cartella esistente
|
|
$response = $this->service->files->listFiles([
|
|
'q' => "name='{$nomeCartella}' and mimeType='application/vnd.google-apps.folder'",
|
|
'spaces' => 'drive'
|
|
]);
|
|
|
|
if (count($response->files) > 0) {
|
|
return $response->files[0]->id;
|
|
}
|
|
|
|
// Crea nuova cartella
|
|
$fileMetadata = new Google_Service_Drive_DriveFile([
|
|
'name' => $nomeCartella,
|
|
'mimeType' => 'application/vnd.google-apps.folder'
|
|
]);
|
|
|
|
$folder = $this->service->files->create($fileMetadata);
|
|
return $folder->id;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 **SISTEMA AUDIT DOCUMENTALE**
|
|
|
|
### **1. Modello Audit Log**
|
|
```php
|
|
// app/Models/AuditDocumento.php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class AuditDocumento extends Model
|
|
{
|
|
protected $table = 'audit_documenti';
|
|
|
|
protected $fillable = [
|
|
'documento_id', 'utente_id', 'azione', 'dettagli',
|
|
'ip_address', 'user_agent', 'dati_precedenti', 'dati_nuovi'
|
|
];
|
|
|
|
protected $casts = [
|
|
'dettagli' => 'array',
|
|
'dati_precedenti' => 'array',
|
|
'dati_nuovi' => 'array'
|
|
];
|
|
|
|
public function documento()
|
|
{
|
|
return $this->belongsTo(Documento::class);
|
|
}
|
|
|
|
public function utente()
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public static function logAzione($documento, $azione, $dettagli = [])
|
|
{
|
|
return self::create([
|
|
'documento_id' => $documento->id,
|
|
'utente_id' => auth()->id(),
|
|
'azione' => $azione,
|
|
'dettagli' => $dettagli,
|
|
'ip_address' => request()->ip(),
|
|
'user_agent' => request()->userAgent(),
|
|
'dati_precedenti' => $documento->getOriginal(),
|
|
'dati_nuovi' => $documento->getAttributes()
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2. Observer per Audit Automatico**
|
|
```php
|
|
// app/Observers/DocumentoObserver.php
|
|
<?php
|
|
|
|
namespace App\Observers;
|
|
|
|
use App\Models\Documento;
|
|
use App\Models\AuditDocumento;
|
|
|
|
class DocumentoObserver
|
|
{
|
|
public function created(Documento $documento)
|
|
{
|
|
AuditDocumento::logAzione($documento, 'created', [
|
|
'messaggio' => 'Documento caricato'
|
|
]);
|
|
}
|
|
|
|
public function updated(Documento $documento)
|
|
{
|
|
$modifiche = [];
|
|
foreach ($documento->getDirty() as $campo => $nuovoValore) {
|
|
$modifiche[$campo] = [
|
|
'da' => $documento->getOriginal($campo),
|
|
'a' => $nuovoValore
|
|
];
|
|
}
|
|
|
|
AuditDocumento::logAzione($documento, 'updated', [
|
|
'messaggio' => 'Documento modificato',
|
|
'modifiche' => $modifiche
|
|
]);
|
|
}
|
|
|
|
public function deleted(Documento $documento)
|
|
{
|
|
AuditDocumento::logAzione($documento, 'deleted', [
|
|
'messaggio' => 'Documento eliminato'
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 **CHECKLIST IMPLEMENTAZIONE**
|
|
|
|
### **Database e Modelli**
|
|
- [ ] Migration tabelle documenti
|
|
- [ ] Migration collegamenti documenti
|
|
- [ ] Migration versioni documenti
|
|
- [ ] Migration audit documenti
|
|
- [ ] Modelli Eloquent completi
|
|
- [ ] Observer per audit automatico
|
|
|
|
### **Servizi Core**
|
|
- [ ] DocumentoService per gestione base
|
|
- [ ] Office365Service per integrazione cloud
|
|
- [ ] GoogleDriveService per integrazione cloud
|
|
- [ ] AuditService per tracciamento completo
|
|
|
|
### **Controller e API**
|
|
- [ ] DocumentoController per CRUD
|
|
- [ ] API per upload tramite drag&drop
|
|
- [ ] API per sincronizzazione cloud
|
|
- [ ] API per ricerca avanzata
|
|
|
|
### **Frontend**
|
|
- [ ] Interfaccia upload documenti
|
|
- [ ] Visualizzatore documenti integrato
|
|
- [ ] Sistema ricerca e filtri
|
|
- [ ] Dashboard audit e statistiche
|
|
|
|
---
|
|
|
|
**Aggiornato**: 2025-01-27
|
|
**Versione**: 1.0
|
|
**Prossimo step**: Implementazione migration e modelli base
|