netgescon-day0/app/Models/Amministratore.php

453 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',
'fe_cassetto_enabled',
'fe_cassetto_expires_at',
'fe_cassetto_base_url',
'fe_cassetto_password_script',
'fe_cassetto_username',
'fe_cassetto_password',
'fe_cassetto_pin',
];
protected $casts = [
'abilita_import_stabili' => 'boolean',
'fe_cassetto_enabled' => 'boolean',
'fe_cassetto_expires_at' => 'datetime',
'fe_cassetto_password_script' => 'encrypted',
'fe_cassetto_username' => 'encrypted',
'fe_cassetto_password' => 'encrypted',
'fe_cassetto_pin' => 'encrypted',
'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_amministratore)) {
$amministratore->codice_amministratore = $amministratore->generateCodiceUnivoco();
}
if (isset($amministratore->codice_univoco) && !Schema::hasColumn('amministratori', 'codice_univoco')) {
unset($amministratore->codice_univoco);
}
});
// Auto-crea cartelle dopo la creazione
static::created(function ($amministratore) {
$amministratore->provisionArchiveIfMissing();
});
}
/**
* 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);
}
}