netgescon-day0/app/Services/DatabaseManagerService.php

652 lines
26 KiB
PHP

<?php
namespace App\Services;
use App\Models\Amministratore;
use App\Models\Stabile;
use Exception;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class DatabaseManagerService
{
public function __construct(private TenantArchivePathService $tenantArchivePath)
{
}
/**
* Crea database dedicato per amministratore
*/
public function createAmministratoreDatabase(Amministratore $amministratore): array
{
$code = $amministratore->codice_amministratore ?? $amministratore->codice_univoco;
$dbName = "netgescon_admin_{$code}";
try {
// 1. Crea database
DB::statement("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
// 2. Aggiorna record amministratore
$amministratore->update(['database_attivo' => $dbName]);
// 3. Crea utente dedicato per database (opzionale, per maggiore sicurezza)
$this->createDatabaseUser($dbName, $code);
// 4. Popola con struttura base
$this->populateAdminDatabase($dbName);
// 5. Crea configurazione dinamica
$this->createDatabaseConfig($amministratore, $dbName);
return [
'success' => true,
'database_name' => $dbName,
'message' => "Database amministratore {$code} creato",
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Crea database dedicato per stabile
*/
public function createStabileDatabase(Stabile $stabile): array
{
$dbName = "netgescon_stabile_{$stabile->codice_stabile}";
$adminCode = $stabile->amministratore->codice_amministratore ?? $stabile->amministratore->codice_univoco;
try {
// 1. Crea database
DB::statement("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
// 2. Aggiorna record stabile
$stabile->update(['database_dedicato' => $dbName]);
// 3. Popola con struttura stabile
$this->populateStabileDatabase($dbName, $stabile);
// 4. Crea cartella fisica per stabile
$this->createStabileFolderStructure($stabile);
return [
'success' => true,
'database_name' => $dbName,
'message' => "Database stabile {$stabile->codice_stabile} creato",
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Crea utente database dedicato (per sicurezza avanzata)
*/
private function createDatabaseUser(string $dbName, string $adminCode): void
{
$username = "user_{$adminCode}";
$password = "pwd_" . substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 12);
try {
// Crea utente
DB::statement("CREATE USER IF NOT EXISTS '{$username}'@'localhost' IDENTIFIED BY '{$password}'");
// Assegna privilegi solo sul database specifico
DB::statement("GRANT ALL PRIVILEGES ON `{$dbName}`.* TO '{$username}'@'localhost'");
DB::statement("FLUSH PRIVILEGES");
// Salva credenziali in file sicuro
$credentialsDir = $this->tenantArchivePath->amministratoreAbsolutePath($adminCode, 'database');
if (! is_dir($credentialsDir)) {
mkdir($credentialsDir, 0755, true);
}
$credentialsPath = $credentialsDir . "/credentials.json";
$credentials = [
'database' => $dbName,
'username' => $username,
'password' => $password,
'host' => 'localhost',
'created_at' => date('Y-m-d H:i:s'),
];
file_put_contents($credentialsPath, json_encode($credentials, JSON_PRETTY_PRINT));
chmod($credentialsPath, 0600); // Solo proprietario può leggere
} catch (Exception $e) {
// Log errore ma non bloccare creazione database
\Log::warning("Impossibile creare utente dedicato per {$adminCode}: " . $e->getMessage());
}
}
/**
* Popola database amministratore con struttura base
*/
private function populateAdminDatabase(string $dbName): void
{
$this->executeOnDatabase($dbName, function () {
$tables = [
// Tabella stabili amministrati
"CREATE TABLE IF NOT EXISTS stabili (
id bigint unsigned NOT NULL AUTO_INCREMENT,
codice_stabile varchar(20) NOT NULL,
denominazione varchar(255) NOT NULL,
indirizzo varchar(255) NOT NULL,
cap varchar(10) NOT NULL,
citta varchar(100) NOT NULL,
provincia varchar(5) NOT NULL,
database_dedicato varchar(100) NULL,
attivo boolean DEFAULT true,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY stabili_codice_unique (codice_stabile)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella fornitori amministratore
"CREATE TABLE IF NOT EXISTS fornitori_admin (
id bigint unsigned NOT NULL AUTO_INCREMENT,
codice_fornitore varchar(20) NOT NULL,
ragione_sociale varchar(255) NOT NULL,
partita_iva varchar(20) NULL,
codice_fiscale varchar(20) NULL,
indirizzo varchar(255) NULL,
cap varchar(10) NULL,
citta varchar(100) NULL,
provincia varchar(5) NULL,
telefono varchar(20) NULL,
email varchar(100) NULL,
note text NULL,
attivo boolean DEFAULT true,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY fornitori_codice_unique (codice_fornitore)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella documenti amministratore
"CREATE TABLE IF NOT EXISTS documenti_admin (
id bigint unsigned NOT NULL AUTO_INCREMENT,
tipo_documento varchar(50) NOT NULL,
numero_documento varchar(50) NOT NULL,
data_documento date NOT NULL,
oggetto varchar(255) NOT NULL,
file_path varchar(500) NULL,
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
KEY documenti_tipo_idx (tipo_documento),
KEY documenti_data_idx (data_documento)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella log operazioni
"CREATE TABLE IF NOT EXISTS log_operazioni (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id bigint unsigned NULL,
operazione varchar(100) NOT NULL,
tabella varchar(100) NOT NULL,
record_id bigint unsigned NULL,
dati_prima json NULL,
dati_dopo json NULL,
ip_address varchar(45) NULL,
user_agent text NULL,
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY log_operazioni_user_idx (user_id),
KEY log_operazioni_data_idx (created_at),
KEY log_operazioni_operazione_idx (operazione)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($tables as $sql) {
DB::statement($sql);
}
});
}
/**
* Popola database stabile con struttura completa
*/
private function populateStabileDatabase(string $dbName, Stabile $stabile): void
{
$this->executeOnDatabase($dbName, function () use ($stabile) {
$tables = [
// Tabella riferimento stabile
"CREATE TABLE IF NOT EXISTS stabili_ref (
id bigint unsigned NOT NULL AUTO_INCREMENT,
codice_stabile varchar(20) NOT NULL,
denominazione varchar(255) NOT NULL,
amministratore_codice varchar(20) NOT NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY stabili_ref_codice_unique (codice_stabile)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella condomini
"CREATE TABLE IF NOT EXISTS condomini (
id bigint unsigned NOT NULL AUTO_INCREMENT,
codice_condominio varchar(20) NOT NULL,
denominazione varchar(255) NOT NULL,
numero_unita int DEFAULT 0,
millesimi_totali decimal(10,3) DEFAULT 1000.000,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY condomini_codice_unique (codice_condominio)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella unità immobiliari
"CREATE TABLE IF NOT EXISTS unita (
id bigint unsigned NOT NULL AUTO_INCREMENT,
condominio_id bigint unsigned NOT NULL,
numero_unita varchar(10) NOT NULL,
piano varchar(10) NULL,
scala varchar(10) NULL,
interno varchar(10) NULL,
superficie decimal(8,2) NULL,
millesimi_proprieta decimal(8,3) NULL,
millesimi_riscaldamento decimal(8,3) NULL,
millesimi_ascensore decimal(8,3) NULL,
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
KEY unita_condominio_idx (condominio_id),
UNIQUE KEY unita_numero_cond_unique (condominio_id, numero_unita)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella proprietari
"CREATE TABLE IF NOT EXISTS proprietari (
id bigint unsigned NOT NULL AUTO_INCREMENT,
nome varchar(100) NOT NULL,
cognome varchar(100) NOT NULL,
codice_fiscale varchar(20) NULL,
data_nascita date NULL,
luogo_nascita varchar(100) NULL,
indirizzo_residenza varchar(255) NULL,
cap_residenza varchar(10) NULL,
citta_residenza varchar(100) NULL,
provincia_residenza varchar(5) NULL,
telefono varchar(20) NULL,
cellulare varchar(20) NULL,
email varchar(100) NULL,
pec varchar(100) NULL,
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
KEY proprietari_cognome_idx (cognome),
KEY proprietari_cf_idx (codice_fiscale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella proprieta (relazione proprietari-unità)
"CREATE TABLE IF NOT EXISTS proprieta (
id bigint unsigned NOT NULL AUTO_INCREMENT,
unita_id bigint unsigned NOT NULL,
proprietario_id bigint unsigned NOT NULL,
percentuale_proprieta decimal(5,2) DEFAULT 100.00,
data_inizio date NOT NULL,
data_fine date NULL,
tipo_diritto enum('proprietà','usufrutto','uso','abitazione','nuda_proprietà') DEFAULT 'proprietà',
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
KEY proprieta_unita_idx (unita_id),
KEY proprieta_proprietario_idx (proprietario_id),
KEY proprieta_data_idx (data_inizio, data_fine)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella fornitori del condominio
"CREATE TABLE IF NOT EXISTS fornitori (
id bigint unsigned NOT NULL AUTO_INCREMENT,
codice_fornitore varchar(20) NOT NULL,
ragione_sociale varchar(255) NOT NULL,
partita_iva varchar(20) NULL,
codice_fiscale varchar(20) NULL,
indirizzo varchar(255) NULL,
cap varchar(10) NULL,
citta varchar(100) NULL,
provincia varchar(5) NULL,
telefono varchar(20) NULL,
email varchar(100) NULL,
categoria varchar(100) NULL,
note text NULL,
attivo boolean DEFAULT true,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY fornitori_codice_unique (codice_fornitore),
KEY fornitori_categoria_idx (categoria)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella movimenti contabili
"CREATE TABLE IF NOT EXISTS movimenti (
id bigint unsigned NOT NULL AUTO_INCREMENT,
numero_movimento varchar(20) NOT NULL,
data_movimento date NOT NULL,
tipo_movimento enum('entrata','uscita') NOT NULL,
fornitore_id bigint unsigned NULL,
causale varchar(255) NOT NULL,
importo decimal(10,2) NOT NULL,
metodo_pagamento varchar(50) NULL,
numero_documento varchar(50) NULL,
data_documento date NULL,
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY movimenti_numero_unique (numero_movimento),
KEY movimenti_data_idx (data_movimento),
KEY movimenti_tipo_idx (tipo_movimento),
KEY movimenti_fornitore_idx (fornitore_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Tabella ripartizioni spese
"CREATE TABLE IF NOT EXISTS ripartizioni (
id bigint unsigned NOT NULL AUTO_INCREMENT,
movimento_id bigint unsigned NOT NULL,
unita_id bigint unsigned NOT NULL,
importo_ripartito decimal(10,2) NOT NULL,
criterio_ripartizione varchar(100) NOT NULL,
millesimi_utilizzati decimal(8,3) NULL,
note text NULL,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id),
KEY ripartizioni_movimento_idx (movimento_id),
KEY ripartizioni_unita_idx (unita_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($tables as $sql) {
DB::statement($sql);
}
// Inserisci dato stabile di riferimento
DB::table('stabili_ref')->insert([
'codice_stabile' => $stabile->codice_stabile,
'denominazione' => $stabile->denominazione,
'amministratore_codice' => $stabile->amministratore->codice_amministratore ?? $stabile->amministratore->codice_univoco,
'created_at' => now(),
'updated_at' => now(),
]);
});
}
/**
* Crea struttura cartelle per stabile
*/
private function createStabileFolderStructure(Stabile $stabile): void
{
$stabileCode = $stabile->codice_stabile;
$basePath = $this->tenantArchivePath->stabileRelativePath($stabile);
$stabileFolders = [
// Documenti stabile
'documenti/contratti',
'documenti/assemblee/verbali',
'documenti/assemblee/convocazioni',
'documenti/assemblee/allegati',
'documenti/amministrazione/delibere',
'documenti/amministrazione/comunicazioni',
'documenti/amministrazione/relazioni',
// Contabilità
'contabilita/fatture/entrate',
'contabilita/fatture/uscite',
'contabilita/ricevute',
'contabilita/bilanci',
'contabilita/ripartizioni',
// Database e backup
'database/backups/daily',
'database/backups/weekly',
'database/backups/monthly',
'database/exports',
// Report
'reports/situazione_contabile',
'reports/morosi',
'reports/movimenti',
'reports/assemblee',
// Archivio
'archivio/per_anno',
'archivio/fatture_pagate',
'archivio/corrispondenza',
// Temp
'temp/upload',
'temp/processing',
'temp/import',
];
foreach ($stabileFolders as $folder) {
Storage::disk('local')->makeDirectory("{$basePath}/{$folder}");
}
// File configurazione stabile
$config = [
'stabile' => [
'codice' => $stabile->codice_stabile,
'denominazione' => $stabile->denominazione,
'amministratore' => $stabile->amministratore->codice_amministratore ?? $stabile->amministratore->codice_univoco,
'database_dedicato' => $stabile->database_dedicato,
'created_at' => $stabile->created_at->toISOString(),
],
'settings' => [
'backup_automatico' => true,
'backup_frequency' => 'daily',
'retention_days' => 90,
'report_automatici' => true,
],
'paths' => [
'documenti' => 'documenti',
'contabilita' => 'contabilita',
'database_backups' => 'database/backups',
'reports' => 'reports',
'archivio' => 'archivio',
],
];
Storage::disk('local')->put(
"{$basePath}/stabile_config.json",
json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
}
/**
* Crea configurazione database dinamica
*/
private function createDatabaseConfig(Amministratore $amministratore, string $dbName): void
{
$code = $amministratore->codice_amministratore ?? $amministratore->codice_univoco;
$configDir = $this->tenantArchivePath->amministratoreAbsolutePath($amministratore, 'database');
if (! is_dir($configDir)) {
mkdir($configDir, 0755, true);
}
$configPath = $configDir . "/config.php";
$configContent = "<?php
return [
'default' => 'admin_mysql',
'connections' => [
'admin_mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => '{$dbName}',
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
'migrations' => 'migrations_admin',
'backup' => [
'path' => storage_path('app/amministratori/{$code}/database/backups'),
'frequency' => 'daily',
'retention' => 30,
],
];
";
file_put_contents($configPath, $configContent);
}
/**
* Esegue operazioni su database specifico
*/
private function executeOnDatabase(string $dbName, callable $callback): void
{
$originalDb = DB::getDatabaseName();
try {
config(['database.connections.mysql.database' => $dbName]);
DB::purge('mysql');
DB::reconnect('mysql');
$callback();
} finally {
config(['database.connections.mysql.database' => $originalDb]);
DB::purge('mysql');
DB::reconnect('mysql');
}
}
/**
* Ottiene connessione a database amministratore
*/
public function getAmministratoreConnection(Amministratore $amministratore): \Illuminate\Database\Connection
{
$dbName = $amministratore->getDatabaseName();
// Crea configurazione dinamica se non esiste
if (! config("database.connections.admin_{$amministratore->codice_univoco}")) {
config([
"database.connections.admin_{$amministratore->codice_univoco}" => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => $dbName,
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
]);
}
return DB::connection("admin_{$amministratore->codice_univoco}");
}
/**
* Ottiene connessione a database stabile
*/
public function getStabileConnection(Stabile $stabile): \Illuminate\Database\Connection
{
$dbName = $stabile->database_dedicato;
if (! $dbName) {
throw new Exception("Stabile {$stabile->codice_stabile} non ha database dedicato");
}
// Crea configurazione dinamica se non esiste
if (! config("database.connections.stabile_{$stabile->codice_stabile}")) {
config([
"database.connections.stabile_{$stabile->codice_stabile}" => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => $dbName,
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
]);
}
return DB::connection("stabile_{$stabile->codice_stabile}");
}
/**
* Backup database amministratore
*/
public function backupAmministratoreDatabase(Amministratore $amministratore): array
{
return $amministratore->createDatabaseBackup();
}
/**
* Backup database stabile
*/
public function backupStabileDatabase(Stabile $stabile): array
{
if (! $stabile->database_dedicato) {
return ['success' => false, 'error' => 'Stabile non ha database dedicato'];
}
try {
// Allinea ai nuovi percorsi per-amministratore basati su codice_amministratore
$backupPath = $this->tenantArchivePath->stabileAbsolutePath($stabile, 'database/backups');
if (! is_dir($backupPath)) {
mkdir($backupPath, 0755, true);
}
$filename = "backup_stabile_{$stabile->codice_stabile}_" . date('Y-m-d_H-i-s') . ".sql";
$fullPath = $backupPath . '/' . $filename;
$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($stabile->database_dedicato),
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(),
];
}
}
}