447 lines
14 KiB
PHP
Executable File
447 lines
14 KiB
PHP
Executable File
<?php
|
|
namespace App\Models;
|
|
|
|
use App\Services\TenantArchivePathService;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany; // Aggiunto per condomini()
|
|
use Illuminate\Database\Eloquent\SoftDeletes; // Aggiunto per soft deletes
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* @property int $id
|
|
* @property string|null $codice_univoco
|
|
* @property string|null $codice_amministratore
|
|
* @property string|null $cartella_dati
|
|
* @property string|null $database_attivo
|
|
* @property bool|null $abilita_import_stabili
|
|
*/
|
|
class Amministratore extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* The table associated with the model.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $table = 'amministratori';
|
|
// Rimossa la primaryKey personalizzata - usa 'id' standard Laravel
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'nome',
|
|
'cognome',
|
|
'denominazione_studio',
|
|
'partita_iva',
|
|
'codice_fiscale_studio',
|
|
'indirizzo_studio',
|
|
'cap_studio',
|
|
'citta_studio',
|
|
'provincia_studio',
|
|
'telefono_studio',
|
|
'email_studio',
|
|
'pec_studio',
|
|
'codice_univoco',
|
|
'codice_amministratore',
|
|
'cartella_dati',
|
|
'database_attivo',
|
|
'abilita_import_stabili',
|
|
];
|
|
|
|
protected $casts = [
|
|
'abilita_import_stabili' => 'boolean',
|
|
'impostazioni' => 'array',
|
|
'provisioned_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Get the user associated with the Amministratore.
|
|
*/
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'user_id');
|
|
}
|
|
|
|
/**
|
|
* Get the condomini for the Amministratore.
|
|
*/
|
|
public function stabili(): HasMany
|
|
{
|
|
return $this->hasMany(Stabile::class, 'amministratore_id', 'id');
|
|
}
|
|
/**
|
|
* Get the fornitori for the Amministratore.
|
|
*/
|
|
public function fornitori(): HasMany
|
|
{
|
|
return $this->hasMany(Fornitore::class, 'amministratore_id', 'id');
|
|
}
|
|
|
|
protected static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::creating(function ($amministratore) {
|
|
if (empty($amministratore->codice_univoco)) {
|
|
do {
|
|
$code = Str::upper(Str::random(8));
|
|
} while (self::where('codice_univoco', $code)->exists());
|
|
$amministratore->codice_univoco = $code;
|
|
}
|
|
});
|
|
|
|
// Auto-crea cartelle dopo la creazione
|
|
static::created(function ($amministratore) {
|
|
$amministratore->provisionArchiveIfMissing();
|
|
});
|
|
|
|
// Aggiorna codice_amministratore per compatibilità
|
|
static::creating(function ($amministratore) {
|
|
if (empty($amministratore->codice_amministratore)) {
|
|
$amministratore->codice_amministratore = $amministratore->codice_univoco ?? $amministratore->generateCodiceUnivoco();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Crea la struttura di cartelle per l'amministratore e aggiorna cartella_dati/database_attivo
|
|
*/
|
|
public function createFolderStructure(): void
|
|
{
|
|
$basePath = $this->getFolderPath();
|
|
|
|
$folders = [
|
|
'documenti/allegati',
|
|
'documenti/contratti',
|
|
'documenti/assemblee',
|
|
'documenti/preventivi',
|
|
'backup/database',
|
|
'backup/files',
|
|
'temp/upload',
|
|
'temp/processing',
|
|
'logs',
|
|
'exports',
|
|
];
|
|
|
|
foreach ($folders as $folder) {
|
|
Storage::disk('local')->makeDirectory("{$basePath}/{$folder}");
|
|
}
|
|
|
|
// Crea file README informativo
|
|
$readme = "# Cartella Amministratore: {$this->nome_completo}\n\n";
|
|
$readme .= "**Codice**: {$this->codice_amministratore}\n";
|
|
$readme .= "**Creato**: " . $this->created_at->format('d/m/Y H:i') . "\n\n";
|
|
$readme .= "## Struttura Cartelle\n\n";
|
|
$readme .= "- `documenti/` - Documenti dell'amministratore\n";
|
|
$readme .= "- `backup/` - Backup automatici\n";
|
|
$readme .= "- `temp/` - File temporanei\n";
|
|
$readme .= "- `logs/` - Log specifici\n";
|
|
$readme .= "- `exports/` - Esportazioni dati\n";
|
|
|
|
Storage::disk('local')->put("{$basePath}/README.md", $readme);
|
|
}
|
|
|
|
/**
|
|
* Se mancano, crea cartelle e imposta cartella_dati; opzionalmente precompila database_attivo
|
|
*/
|
|
public function provisionArchiveIfMissing(): void
|
|
{
|
|
// Cartella principale
|
|
$basePath = storage_path('app/' . $this->getFolderPath());
|
|
if (! is_dir($basePath)) {
|
|
$this->createFolderStructure();
|
|
}
|
|
// Aggiorna colonna cartella_dati se vuota
|
|
if (empty($this->cartella_dati)) {
|
|
$this->cartella_dati = $basePath;
|
|
}
|
|
// Prepara nome DB dedicato (non crea fisicamente il DB qui)
|
|
if (empty($this->database_attivo)) {
|
|
$this->database_attivo = 'netgescon_admin_' . strtolower((string) $this->codice_amministratore);
|
|
}
|
|
// Salva senza toccare timestamps e trigger (evita loop)
|
|
$this->saveQuietly();
|
|
}
|
|
|
|
/**
|
|
* Ottieni il path della cartella dell'amministratore
|
|
*/
|
|
public function getFolderPath(): string
|
|
{
|
|
return app(TenantArchivePathService::class)->amministratoreRelativePath($this);
|
|
}
|
|
|
|
/**
|
|
* Genera codice univoco alfanumerico 8 caratteri
|
|
*/
|
|
public function generateCodiceUnivoco(): string
|
|
{
|
|
do {
|
|
$codice = 'ADM' . strtoupper(substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 5));
|
|
} while (self::where('codice_amministratore', $codice)->exists());
|
|
|
|
return $codice;
|
|
}
|
|
|
|
/**
|
|
* Verifica se l'amministratore ha un database dedicato attivo
|
|
*/
|
|
public function hasDedicatedDatabase(): bool
|
|
{
|
|
return ! empty($this->database_attivo);
|
|
}
|
|
|
|
/**
|
|
* Ottiene il nome del database dedicato per questo amministratore
|
|
*/
|
|
public function getDatabaseName(): string
|
|
{
|
|
return $this->database_attivo ?: "netgescon_admin_{$this->codice_amministratore}";
|
|
}
|
|
|
|
/**
|
|
* Ottiene il percorso fisico dell'archivio amministratore
|
|
*/
|
|
public function getArchivePath(): string
|
|
{
|
|
return app(TenantArchivePathService::class)->amministratoreAbsolutePath($this);
|
|
}
|
|
|
|
/**
|
|
* Ottiene il percorso del backup database
|
|
*/
|
|
public function getDatabaseBackupPath(): string
|
|
{
|
|
return $this->getArchivePath() . '/backup/database';
|
|
}
|
|
|
|
/**
|
|
* Verifica se l'archivio fisico esiste
|
|
*/
|
|
public function archiveExists(): bool
|
|
{
|
|
return is_dir($this->getArchivePath());
|
|
}
|
|
|
|
/**
|
|
* Ottiene informazioni dettagliate sull'archivio
|
|
*/
|
|
public function getArchiveInfo(): array
|
|
{
|
|
$archivePath = $this->getArchivePath();
|
|
|
|
if (! $this->archiveExists()) {
|
|
return [
|
|
'exists' => false,
|
|
'path' => $archivePath,
|
|
'size' => 0,
|
|
'files_count' => 0,
|
|
'last_backup' => null,
|
|
];
|
|
}
|
|
|
|
$size = 0;
|
|
$filesCount = 0;
|
|
$lastBackup = null;
|
|
|
|
// Calcola dimensione totale e numero file
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($archivePath)
|
|
);
|
|
|
|
foreach ($iterator as $file) {
|
|
if ($file->isFile()) {
|
|
$size += $file->getSize();
|
|
$filesCount++;
|
|
|
|
// Trova ultimo backup database
|
|
if (
|
|
str_contains($file->getPathname(), 'backup/database') &&
|
|
str_ends_with($file->getFilename(), '.sql')
|
|
) {
|
|
$backupTime = filemtime($file->getPathname());
|
|
if (! $lastBackup || $backupTime > $lastBackup) {
|
|
$lastBackup = $backupTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'exists' => true,
|
|
'path' => $archivePath,
|
|
'size' => $size,
|
|
'size_formatted' => $this->formatBytes($size),
|
|
'files_count' => $filesCount,
|
|
'last_backup' => $lastBackup ? date('Y-m-d H:i:s', $lastBackup) : null,
|
|
'directories' => [
|
|
'documenti' => is_dir($archivePath . '/documenti'),
|
|
'backup' => is_dir($archivePath . '/backup'),
|
|
'temp' => is_dir($archivePath . '/temp'),
|
|
'logs' => is_dir($archivePath . '/logs'),
|
|
'exports' => is_dir($archivePath . '/exports'),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Formatta bytes in formato leggibile
|
|
*/
|
|
private function formatBytes(int $bytes, int $precision = 2): string
|
|
{
|
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
|
|
$bytes /= 1024;
|
|
}
|
|
|
|
return round($bytes, $precision) . ' ' . $units[$i];
|
|
}
|
|
|
|
/**
|
|
* Crea backup del database amministratore
|
|
*/
|
|
public function createDatabaseBackup(): array
|
|
{
|
|
try {
|
|
if (! $this->hasDedicatedDatabase()) {
|
|
throw new \Exception('Amministratore non ha database dedicato');
|
|
}
|
|
|
|
$backupPath = $this->getDatabaseBackupPath();
|
|
if (! is_dir($backupPath)) {
|
|
mkdir($backupPath, 0755, true);
|
|
}
|
|
|
|
$filename = "backup_" . $this->codice_amministratore . "_" . date('Y-m-d_H-i-s') . ".sql";
|
|
$fullPath = $backupPath . '/' . $filename;
|
|
|
|
$dbName = $this->getDatabaseName();
|
|
$command = sprintf(
|
|
'mysqldump -h %s -u %s -p%s %s > %s',
|
|
env('DB_HOST', '127.0.0.1'),
|
|
env('DB_USERNAME'),
|
|
env('DB_PASSWORD'),
|
|
escapeshellarg($dbName),
|
|
escapeshellarg($fullPath)
|
|
);
|
|
|
|
exec($command, $output, $returnCode);
|
|
|
|
if ($returnCode !== 0) {
|
|
throw new \Exception('Errore durante backup database: ' . implode("\n", $output));
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'filename' => $filename,
|
|
'path' => $fullPath,
|
|
'size' => filesize($fullPath),
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepara archivio per migrazione/trasferimento
|
|
*/
|
|
public function prepareForMigration(): array
|
|
{
|
|
try {
|
|
// 1. Backup database
|
|
$dbBackup = $this->createDatabaseBackup();
|
|
if (! $dbBackup['success']) {
|
|
throw new \Exception('Errore backup database: ' . $dbBackup['error']);
|
|
}
|
|
|
|
// 2. Crea archivio ZIP dell'intera cartella
|
|
$archivePath = $this->getArchivePath();
|
|
$zipFilename = "migration_" . $this->codice_amministratore . "_" . date('Y-m-d_H-i-s') . ".zip";
|
|
$zipPath = storage_path("app/migrations/{$zipFilename}");
|
|
|
|
if (! is_dir(dirname($zipPath))) {
|
|
mkdir(dirname($zipPath), 0755, true);
|
|
}
|
|
|
|
$zip = new \ZipArchive();
|
|
if ($zip->open($zipPath, \ZipArchive::CREATE) !== true) {
|
|
throw new \Exception('Impossibile creare archivio ZIP');
|
|
}
|
|
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($archivePath)
|
|
);
|
|
|
|
foreach ($iterator as $file) {
|
|
if ($file->isFile()) {
|
|
$relativePath = str_replace($archivePath . DIRECTORY_SEPARATOR, '', $file->getPathname());
|
|
$zip->addFile($file->getPathname(), $relativePath);
|
|
}
|
|
}
|
|
|
|
$zip->close();
|
|
|
|
// 3. Crea file metadata per migrazione
|
|
$metadata = [
|
|
'amministratore' => [
|
|
'codice' => $this->codice_amministratore,
|
|
'nome' => $this->nome,
|
|
'cognome' => $this->cognome,
|
|
'denominazione_studio' => $this->denominazione_studio,
|
|
'database_name' => $this->getDatabaseName(),
|
|
],
|
|
'migration' => [
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'source_server' => env('APP_URL'),
|
|
'database_backup' => $dbBackup['filename'],
|
|
'archive_size' => filesize($zipPath),
|
|
'files_count' => $this->getArchiveInfo()['files_count'],
|
|
],
|
|
'requirements' => [
|
|
'php_version' => PHP_VERSION,
|
|
'laravel_version' => app()->version(),
|
|
'mysql_version' => DB::select('SELECT VERSION() as version')[0]->version,
|
|
],
|
|
];
|
|
|
|
$metadataPath = dirname($zipPath) . "/metadata_{$this->codice_amministratore}.json";
|
|
file_put_contents($metadataPath, json_encode($metadata, JSON_PRETTY_PRINT));
|
|
|
|
return [
|
|
'success' => true,
|
|
'zip_file' => $zipPath,
|
|
'metadata_file' => $metadataPath,
|
|
'size' => filesize($zipPath),
|
|
'database_backup' => $dbBackup['filename'],
|
|
];
|
|
} catch (\Exception $e) {
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attributo computed per nome completo
|
|
*/
|
|
public function getNomeCompletoAttribute(): string
|
|
{
|
|
return trim($this->nome . ' ' . $this->cognome);
|
|
}
|
|
}
|