🚀 NETGESCON - Laravel completo integrato nel repository principale

This commit is contained in:
Michele Windows 2025-07-20 16:48:35 +02:00
parent 0e87d5ec4c
commit 1c3cf4c0c9
755 changed files with 838462 additions and 2 deletions

@ -1 +0,0 @@
Subproject commit 2d6fba0e605ae7af4d3130cc6085f1540a03b111

View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

View File

@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mdb_archivio
DB_USERNAME=root
DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
netgescon-laravel/.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

12
netgescon-laravel/.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# Supporta NetGesCon
Se vuoi sostenere lo sviluppo di NetGesCon, puoi farlo tramite i seguenti canali:
- [PayPal](https://www.paypal.com/donate/?hosted_button_id=NPBKFSJCEVSLN)
- [Patreon](https://patreon.com/netgescon)
Grazie per il tuo supporto!
paypal: NPBKFSJCEVSLN
patreon: netgescon
custom: ["https://www.paypal.com/donate/?hosted_button_id=NPBKFSJCEVSLN", "https://patreon.com/netgescon"]

116
netgescon-laravel/.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/app/public
/storage/framework
/storage/logs
/venv
/vendor
Homestead.json
Homestead.yaml
npm-debug.log
Thumbs.db
yarn-error.log
# NetGesCon specifici - File sensibili
.env*
!.env.example
.env.seeder
# Dati amministratori e backup
/storage/app/amministratori/*/
/storage/app/backup/
/storage/app/temp/
# Password e chiavi private
*.key
*.pem
*.p12
*.pfx
*_rsa
*_dsa
*_ecdsa
*_ed25519
# ============================================
# DOCUMENTAZIONE INTERNA - NON PUBBLICA
# ============================================
# File di lavoro interno - Da escludere fino a stabilizzazione
PROGRESS_LOG.md
DATABASE_SCHEMA.md
DATA_ARCHITECTURE.md
API_ENDPOINTS.md
UI_COMPONENTS.md
DEVELOPMENT_IDEAS.md
# Sistema aggiornamenti - In sviluppo
UPDATE_SYSTEM.md
DISTRIBUTION_SYSTEM.md
# NetGesCon - Documentazione interna (non per repository pubblico)
/docs/specifiche/
/docs/logs/
/docs/checklist/
# File di workflow e comunicazione interna
/docs/PROTOCOLLO_COMUNICAZIONE.md
/docs/PROCEDURA_OPERATIVA.md
/docs/QUICK_REFERENCE.md
/docs/miki.md
# Mantieni solo guide pubbliche e README
!/docs/README.md
!/docs/guide/
!/docs/guide/api-guide.md
!/docs/guide/install-guide.md
# File di sviluppo temporanei
TODO_*.md
TEMP_*.md
DRAFT_*.md
# Log di sviluppo personali
*_LOG.txt
*_NOTES.txt
SESSION_*.md
PROGRESS_*.md
# ============================================
# DOCUMENTAZIONE PUBBLICA - DA INCLUDERE
# ============================================
# Questi file SONO inclusi nel repository:
# README.md
# INSTALL_LINUX.md
# CONTRIBUTING.md (se creato)
# LICENSE (se creato)
# CHANGELOG.md (quando sarà pronto)
# Backup e dump database
*.sql
*.dump
*.backup
# File di sviluppo
.phpunit.result.cache
.pest.result.cache
coverage/
*.coverage

View File

@ -0,0 +1,21 @@
.git/
node_modules/
vendor/
venv/
storage/logs/
storage/framework/cache/
storage/framework/sessions/
storage/framework/views/
bootstrap/cache/
database/schema/
.env
.env.local
.env.example
*.log
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.DS_Store
Thumbs.db

View File

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\Amministratore;
use App\Models\User;
use Spatie\Permission\Models\Role; // Aggiunto per Role
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth; // Aggiunto per Auth
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Gate; // Aggiunto per Gate
class AmministratoreController extends Controller
{
public function __construct()
{
// Proteggi le rotte con i permessi di Spatie
$this->middleware('permission:view-amministratori', ['only' => ['index']]); // Permesso per visualizzare la lista
$this->middleware('permission:manage-amministratori', ['except' => ['index', 'show']]); // Permesso per tutte le altre azioni CRUD
}
/**
* Display a listing of the resource.
*/
public function index()
{
// Gate::authorize('view-amministratori'); // Il middleware nel costruttore è sufficiente
$amministratori = Amministratore::with('user')->paginate(10);
return view('superadmin.amministratori.index', compact('amministratori'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
// Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente
$usersWithoutAdminRole = User::doesntHave('amministratore')->get(); // Utenti non ancora associati a un amministratore
return view('superadmin.amministratori.create', compact('usersWithoutAdminRole'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email',
'password' => 'required|string|min:8|confirmed',
'nome' => 'required|string|max:255',
'cognome' => 'required|string|max:255',
'denominazione_studio' => 'nullable|string|max:255',
'partita_iva' => 'nullable|string|max:20|unique:amministratori,partita_iva',
'codice_fiscale_studio' => 'nullable|string|max:20',
'indirizzo_studio' => 'nullable|string|max:255',
'cap_studio' => 'nullable|string|max:10',
'citta_studio' => 'nullable|string|max:60',
'provincia_studio' => 'nullable|string|max:2',
'telefono_studio' => 'nullable|string|max:20',
'email_studio' => 'nullable|string|email|max:255',
'pec_studio' => 'nullable|string|email|max:255',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'email_verified_at' => now(),
]);
$user->assignRole('admin'); // Assegna il ruolo 'admin' al nuovo utente per coerenza con le rotte
Amministratore::create([
'user_id' => $user->id,
'nome' => $request->nome,
'cognome' => $request->cognome,
'denominazione_studio' => $request->denominazione_studio,
'partita_iva' => $request->partita_iva,
'codice_fiscale_studio' => $request->codice_fiscale_studio,
'indirizzo_studio' => $request->indirizzo_studio,
'cap_studio' => $request->cap_studio,
'citta_studio' => $request->citta_studio,
'provincia_studio' => $request->provincia_studio,
'telefono_studio' => $request->telefono_studio,
'email_studio' => $request->email_studio,
'pec_studio' => $request->pec_studio,
]);
return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore creato con successo.');
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Amministratore $amministratore) // Aggiunto metodo edit
{
// Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente
// Recupera gli utenti che non sono ancora collegati a un record Amministratore
$usersWithoutAdminRole = User::doesntHave('amministratore')->get();
// Includi l'utente attualmente collegato a questo amministratore nella lista
$usersWithoutAdminRole = $usersWithoutAdminRole->merge([$amministratore->user]);
return view('superadmin.amministratori.edit', compact('amministratore', 'usersWithoutAdminRole'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Amministratore $amministratore)
{
// Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente
$request->validate([
'user_id' => 'required|exists:users,id|unique:amministratori,user_id,' . $amministratore->id_amministratore . ',id_amministratore',
'nome' => 'required|string|max:255',
'cognome' => 'required|string|max:255',
'denominazione_studio' => 'nullable|string|max:255',
'partita_iva' => ['nullable', 'string', 'max:20', Rule::unique('amministratori')->ignore($amministratore->id_amministratore, 'id_amministratore')], // Corretto id a id_amministratore
'codice_fiscale_studio' => 'nullable|string|max:20',
'indirizzo_studio' => 'nullable|string|max:255',
'cap_studio' => 'nullable|string|max:10',
'citta_studio' => 'nullable|string|max:255',
'provincia_studio' => 'nullable|string|max:2',
'telefono_studio' => 'nullable|string|max:20',
'email_studio' => 'nullable|email|max:255',
'pec_studio' => 'nullable|email|max:255',
]);
// Aggiorna i dati dell'amministratore
$amministratore->update($request->all());
return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore aggiornato con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Amministratore $amministratore)
{
// Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente
$amministratore->user->delete(); // Elimina anche l'utente associato
$amministratore->delete();
return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore eliminato con successo.');
}
}

1
netgescon-laravel/Database/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class AmministratoreSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Chiama solo il seeder che abbiamo creato.
$this->call([
// SuperAdminSeeder::class, // Questo seeder è ora inglobato in TestSetupSeeder
\App\Console\Seeders\TestSetupSeeder::class, // Chiama il seeder principale di setup
ImpostazioniSeeder::class,
\App\Console\Seeders\AllegatiSeeder::class, // Seeder per allegati con struttura moderna
]);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DemoDataSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ImpostazioniSeeder extends Seeder
{
public function run(): void
{
DB::table('impostazioni')->insertOrIgnore([
[
'chiave' => 'sidebar_bg',
'valore' => '#fde047',
'descrizione' => 'Colore di sfondo sidebar (giallo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text',
'valore' => '#1e293b',
'descrizione' => 'Colore testo sidebar',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent',
'valore' => '#6366f1',
'descrizione' => 'Colore accento sidebar (indigo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_bg_dark',
'valore' => '#23272e',
'descrizione' => 'Colore sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text_dark',
'valore' => '#f1f5f9',
'descrizione' => 'Colore testo sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent_dark',
'valore' => '#fbbf24',
'descrizione' => 'Colore accento sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class MovimentiContabiliSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$stabili = \App\Models\Stabile::take(3)->get();
$gestioni = \App\Models\Gestione::take(2)->get();
$fornitori = \App\Models\Fornitore::take(5)->get();
$users = \App\Models\User::take(2)->get();
if ($stabili->isEmpty() || $gestioni->isEmpty() || $users->isEmpty()) {
$this->command->info('Skipping MovimentiContabiliSeeder: missing related data');
return;
}
$movimenti = [
// Prima nota - da confermare
[
'stabile_id' => $stabili->first()->id_stabile,
'gestione_id' => $gestioni->first()->id,
'fornitore_id' => $fornitori->isNotEmpty() ? $fornitori->first()->id : null,
'stato_movimento' => 'prima_nota',
'data_registrazione' => now()->subDays(5),
'descrizione' => 'Fattura ENEL - Energia elettrica parti comuni',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'ordinario',
'importo_totale' => 450.00,
'iva' => 45.00,
'importo_netto' => 495.00,
'numero_documento' => 'FAT-2024-001',
'data_documento' => now()->subDays(7),
'creato_da' => $users->first()->id,
],
[
'stabile_id' => $stabili->first()->id_stabile,
'gestione_id' => $gestioni->first()->id,
'stato_movimento' => 'prima_nota',
'data_registrazione' => now()->subDays(3),
'descrizione' => 'Rate condominiali gennaio 2025',
'tipo_movimento' => 'entrata',
'categoria_movimento' => 'ordinario',
'importo_totale' => 2500.00,
'importo_netto' => 2500.00,
'creato_da' => $users->first()->id,
],
// Movimenti confermati
[
'stabile_id' => $stabili->first()->id_stabile,
'gestione_id' => $gestioni->first()->id,
'fornitore_id' => $fornitori->count() > 1 ? $fornitori->skip(1)->first()->id : null,
'stato_movimento' => 'confermato',
'data_registrazione' => now()->subDays(10),
'data_conferma' => now()->subDays(8),
'confermato_da' => $users->count() > 1 ? $users->skip(1)->first()->id : $users->first()->id,
'descrizione' => 'Pulizia scale - Ditta XYZ',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'ordinario',
'importo_totale' => 300.00,
'iva' => 30.00,
'importo_netto' => 330.00,
'numero_documento' => 'FAT-2024-002',
'data_documento' => now()->subDays(12),
'creato_da' => $users->first()->id,
],
// Movimento straordinario
[
'stabile_id' => $stabili->count() > 1 ? $stabili->skip(1)->first()->id_stabile : $stabili->first()->id_stabile,
'gestione_id' => $gestioni->count() > 1 ? $gestioni->skip(1)->first()->id : $gestioni->first()->id,
'stato_movimento' => 'confermato',
'data_registrazione' => now()->subDays(15),
'data_conferma' => now()->subDays(12),
'confermato_da' => $users->first()->id,
'descrizione' => 'Riparazione ascensore - Intervento urgente',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'straordinario',
'importo_totale' => 1500.00,
'iva' => 150.00,
'importo_netto' => 1650.00,
'numero_documento' => 'FAT-2024-003',
'data_documento' => now()->subDays(16),
'note_interne' => 'Intervento urgente per guasto improvviso',
'creato_da' => $users->first()->id,
],
// Girofondi
[
'stabile_id' => $stabili->first()->id_stabile,
'gestione_id' => $gestioni->first()->id,
'stato_movimento' => 'confermato',
'data_registrazione' => now()->subDays(20),
'data_conferma' => now()->subDays(18),
'confermato_da' => $users->first()->id,
'descrizione' => 'Trasferimento da fondo ordinario a fondo straordinario',
'tipo_movimento' => 'girofondi',
'categoria_movimento' => 'fondo',
'importo_totale' => 1000.00,
'importo_netto' => 1000.00,
'note_interne' => 'Delibera assembleare N.5/2024',
'creato_da' => $users->first()->id,
]
];
foreach ($movimenti as $movimento) {
\App\Models\MovimentoContabile::create($movimento);
}
$this->command->info('Creati ' . count($movimenti) . ' movimenti contabili di test');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class NewTestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class SuperAdminSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Cerca se l'utente esiste già per evitare duplicati
if (!User::where('email', 'superadmin@example.com')->exists()) {
User::create([
'name' => 'Super Admin',
'email' => 'superadmin@example.com',
'password' => Hash::make('password'), // Cambiare in produzione!
'role' => 'super-admin',
]);
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class SuperAdminSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Cerca se l'utente esiste già per evitare duplicati
if (!User::where('email', 'superadmin@example.com')->exists()) {
User::create([
'name' => 'Super Admin',
'email' => 'superadmin@example.com',
'password' => Hash::make('password'), // Cambiare in produzione!
'role' => 'super-admin',
]);
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TestSeeder2 extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,372 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Amministratore;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\Soggetto;
use App\Models\Proprieta;
use App\Models\TabellaMillesimale;
use App\Models\DettaglioTabellaMillesimale;
use App\Models\PianoContiCondominio;
use App\Models\Gestione;
use App\Models\Preventivo;
use App\Models\VocePreventivo;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Illuminate\Support\Facades\Hash;
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
class TestSetupSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pulisce la cache dei permessi
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 1. Crea i ruoli
// Usa Spatie\Permission\Models\Role per assegnare i ruoli
$superAdminRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'super-admin', 'guard_name' => 'web']);
// Ruoli in italiano per la gestione condominiale
$amministratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
$collaboratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'collaboratore', 'guard_name' => 'web']);
$condominoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'condomino', 'guard_name' => 'web']);
$fornitoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'fornitore', 'guard_name' => 'web']);
$inquilinoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'inquilino', 'guard_name' => 'web']);
$ospiteRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'ospite', 'guard_name' => 'web']);
$serviziRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'servizi', 'guard_name' => 'web']);
$this->command->info('Ruoli creati/verificati.');
// Ruoli di base per sviluppo (rimosso uso di App\Models\Role e campo label)
// Tutti i ruoli sono ora gestiti solo tramite Spatie\Permission\Models\Role
// 2. Crea l'utente Super Admin
// Rimosso il campo 'role' diretto, verrà assegnato tramite Spatie
$superAdmin = User::firstOrCreate(
['email' => 'superadmin@example.com'],
[
'name' => 'Super Admin',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'super-admin' verrà assegnato tramite Spatie
$this->command->info('Utente Super Admin creato/aggiornato: ' . $superAdmin->email); // Variabile corretta
// 2. Crea un Utente Amministratore
$adminUser = User::firstOrCreate(
['email' => 'admin@example.com'],
[
'name' => 'Amministratore Test',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'admin' verrà assegnato tramite Spatie
$this->command->info('Utente Amministratore creato/aggiornato: ' . $adminUser->email);
// 3. Crea un Record Amministratore (collegato all'utente admin)
$amministratore = Amministratore::firstOrCreate(
['user_id' => $adminUser->id],
[
'nome' => 'Mario',
'cognome' => 'Rossi',
'denominazione_studio' => 'Studio Rossi Amministrazioni',
'partita_iva' => '12345678901',
'codice_fiscale_studio' => 'RSSMRA80A01H501K',
'indirizzo_studio' => 'Via Roma 10',
'cap_studio' => '00100',
'citta_studio' => 'Roma',
'provincia_studio' => 'RM',
'telefono_studio' => '061234567',
'email_studio' => 'studio.rossi@example.com',
'pec_studio' => 'studio.rossi@pec.it',
]
);
$this->command->info('Record Amministratore creato/aggiornato: ' . $amministratore->nome . ' ' . $amministratore->cognome);
// 4. Crea un Condominio di Test
$stabile = Stabile::firstOrCreate(
['denominazione' => 'Stabile Test Via Milano 1'],
[
'amministratore_id' => $amministratore->id, // Usa 'id' invece di 'id_amministratore'
'indirizzo' => 'Via Milano 1',
'cap' => '20100',
'citta' => 'Milano',
'provincia' => 'MI',
'codice_fiscale' => 'CNDMLN00001A001A',
'note' => 'Condominio di test per lo sviluppo.',
'stato' => 'attivo',
]
);
$this->command->info('Stabile di Test creato/aggiornato: ' . $stabile->denominazione);
// 5. Crea Unità Immobiliari di Test
$unita1 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '1', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '1',
'categoria_catastale' => 'A/3',
'superficie' => 80.50,
'vani' => 4.5,
'indirizzo' => null,
'note' => 'Appartamento di test A1',
]
);
$unita2 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '2', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '2',
'categoria_catastale' => 'A/3',
'superficie' => 70.00,
'vani' => 3.5,
'indirizzo' => null,
'note' => 'Appartamento di test A2',
]
);
$this->command->info('Unità Immobiliari di Test create.');
// 6. Crea Soggetti di Test
$soggettoProprietario1 = Soggetto::firstOrCreate(['email' => 'proprietario1@example.com'], ['nome' => 'Giuseppe', 'cognome' => 'Verdi', 'tipo' => 'proprietario', 'codice_fiscale' => 'VRDGPP80A01H501A']);
$soggettoProprietario2 = Soggetto::firstOrCreate(['email' => 'proprietario2@example.com'], ['nome' => 'Maria', 'cognome' => 'Bianchi', 'tipo' => 'proprietario', 'codice_fiscale' => 'BNCMRA85B02H502B']);
$soggettoInquilino = Soggetto::firstOrCreate(['email' => 'inquilino@example.com'], ['nome' => 'Luca', 'cognome' => 'Neri', 'tipo' => 'inquilino', 'codice_fiscale' => 'NRELCA90C03H503C']);
$this->command->info('Soggetti di Test creati.');
// 7. Collega Soggetti alle Unità (Proprieta)
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2020-01-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'nudo_proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario2->id ?? $soggettoProprietario2->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'usufruttuario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoInquilino->id ?? $soggettoInquilino->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'inquilino',
'percentuale_possesso' => 100.00,
'data_inizio' => '2023-06-15'
]);
$this->command->info('Relazioni Soggetto-Unità create.');
// 8. Crea una Tabella Millesimale di Test
$tabellaA = TabellaMillesimale::firstOrCreate(
['stabile_id' => $stabile->id, 'nome_tabella_millesimale' => 'Tabella A - Proprietà'],
['descrizione' => 'Ripartizione spese in base ai millesimi di proprietà generale.']
);
// Fix: recupera la chiave primaria corretta se non presente
if (!$tabellaA->id) {
// Prova a ricaricare dal DB se firstOrCreate restituisce un oggetto senza la chiave primaria
$tabellaA = TabellaMillesimale::where('stabile_id', $stabile->id)
->where('nome_tabella_millesimale', 'Tabella A - Proprietà')
->first();
}
if (!$tabellaA || !$tabellaA->id) {
$this->command->error('Errore: la tabella millesimale non è stata creata correttamente!');
return;
}
$this->command->info('Tabella Millesimale di Test creata.');
// 9. Crea Dettagli Millesimali per le unità
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita],
['millesimi' => 500.0000]
);
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita],
['millesimi' => 500.0000]
);
$this->command->info('Dettagli Millesimali creati.');
/*// 10. Crea una Gestione di Test
$gestione2024 = Gestione::firstOrCreate(
['stabile_id' => $stabile->id, 'anno' => 2024, 'tipo' => 'ORDINARIA'],
['data_inizio' => '2024-01-01', 'data_fine' => '2024-12-31', 'stato' => 'aperta']
);
$this->command->info('Gestione di Test creata.');*/
// 11. Crea un Piano dei Conti per lo Stabile (esempio base)
$contoPulizie = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.PUL'],
['descrizione' => 'Spese di Pulizia Scale', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$contoAssicurazione = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.ASS'],
['descrizione' => 'Assicurazione Fabbricato', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$this->command->info('Piano dei Conti di Test creato.');
/*// 12. Crea un Preventivo di Test
$preventivo2024 = Preventivo::firstOrCreate(
['id_gestione' => $gestione2024->id_gestione],
['descrizione' => 'Preventivo Ordinario 2024', 'stato' => 'APPROVATO']
);
$this->command->info('Preventivo di Test creato.');
// 13. Crea Voci di Preventivo
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoPulizie->id_conto_condominio_pc], ['importo_previsto' => 1200.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoAssicurazione->id_conto_condominio_pc], ['importo_previsto' => 800.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
$this->command->info('Voci di Preventivo create.'); */
// Creazione Permessi (Esempio)
$gestioneCondominiPermission = Permission::firstOrCreate(['name' => 'gestione-condomini']);
$visualizzaReportPermission = Permission::firstOrCreate(['name' => 'visualizza-report']);
Permission::firstOrCreate(['name' => 'view-stabili']);
Permission::firstOrCreate(['name' => 'manage-stabili']); // Permesso generico per le azioni CRUD
// Permessi per la gestione utenti (Super Admin)
Permission::firstOrCreate(['name' => 'create-users']);
Permission::firstOrCreate(['name' => 'view-users']);
Permission::firstOrCreate(['name' => 'manage-users']); // Include create, edit, delete, update role
Permission::firstOrCreate(['name' => 'impersonate-users']);
// Permessi per la gestione amministratori (Super Admin)
Permission::firstOrCreate(['name' => 'view-amministratori']);
Permission::firstOrCreate(['name' => 'manage-amministratori']); // Include create, edit, delete
// Permessi per la gestione categorie ticket (Super Admin)
Permission::firstOrCreate(['name' => 'view-categorie-ticket']);
Permission::firstOrCreate(['name' => 'manage-categorie-ticket']); // Include create, edit, delete
// Permessi per la gestione soggetti (Admin)
Permission::firstOrCreate(['name' => 'view-soggetti']);
Permission::firstOrCreate(['name' => 'manage-soggetti']); // Include create, edit, delete
// Permessi per la gestione fornitori (Admin)
Permission::firstOrCreate(['name' => 'view-fornitori']);
Permission::firstOrCreate(['name' => 'manage-fornitori']);
// Permessi per la gestione ticket (Admin)
Permission::firstOrCreate(['name' => 'view-tickets']);
Permission::firstOrCreate(['name' => 'manage-tickets']);
// Permessi per la gestione unità immobiliari (Admin)
Permission::firstOrCreate(['name' => 'view-unita-immobiliari']);
Permission::firstOrCreate(['name' => 'manage-unita-immobiliari']);
// Permessi per le impostazioni e API Tokens (Admin)
Permission::firstOrCreate(['name' => 'view-impostazioni']);
Permission::firstOrCreate(['name' => 'manage-api-tokens']);
Permission::firstOrCreate(['name' => 'view-rubrica']);
// Aggiungi qui altri permessi specifici per il tuo progetto
// Assegnazione Permessi ai Ruoli (Esempio)
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
$adminRole = \Spatie\Permission\Models\Role::where('name', 'admin')->first();
$superAdminRole = \Spatie\Permission\Models\Role::where('name', 'super-admin')->first();
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
if ($amministratoreRole) {
$amministratoreRole->givePermissionTo([
'visualizza-report',
'view-stabili', 'manage-stabili',
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: permessi non assegnati.");
}
// Assegna i permessi al ruolo 'admin'
$adminRole->givePermissionTo([
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
// Assegna il ruolo 'amministratore' all'utente di test per permettergli di gestire gli stabili
if ($amministratoreRole) {
$adminUser->assignRole('amministratore');
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: non assegnato all'utente di test.");
}
// Assegna tutti i permessi al Super Admin
$superAdminRole->givePermissionTo(Permission::all());
$superAdmin->assignRole('super-admin');
$this->command->info('Setup di test completato con successo!');
}
}
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class UserRoleSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CondominioController;
/*
|--------------------------------------------------------------------------
Unchanged linesRoute::get('/', function () {
return view('welcome');
});
Route::middleware(['auth', 'verified'])->group(function () {
// Aggiungi qui altre rotte protette
Route::resource('condomini', CondominioController::class);
});

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'stabili' è ora gestita dalla migration unificata delle anagrafiche.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'proprieta' e le relative FK sono ora gestite dalla migration unificata delle anagrafiche o da una migration master dedicata.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione delle tabelle di contabilità e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'richieste_modifiche' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'preventivi' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'bilanci' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'assemblee' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'fornitori' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'conti_condominio' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'tabelle_millesimali' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'dettagli_tabelle_millesimali' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'gestioni' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasTable('voci_spesa')) {
Schema::create('voci_spesa', function (Blueprint $table) {
$table->bigIncrements('id_voce');
$table->string('codice')->nullable()->unique();
$table->string('descrizione');
$table->string('tipo', 50)->nullable()->comment('ordinaria/straordinaria/riscaldamento/altro');
$table->text('note')->nullable();
$table->timestamps();
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('voci_spesa');
}
};
// Questo file crea la tabella 'voci_spesa' per gestire le voci di spesa nel gestionale.
// La tabella include:
// - id_voce: ID univoco della voce di spesa
// - codice: Codice univoco della voce di spesa
// - descrizione: Descrizione della voce di spesa
// - tipo: Tipo di spesa (ordinaria, straordinaria, riscaldamento, altro)
// - note: Note aggiuntive sulla voce di spesa
// - timestamps: Campi created_at e updated_at per la gestione delle date
//

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('piani_conti_modello', function (Blueprint $table) {
$table->bigIncrements('id'); // PK uniformata
$table->string('codice', 20)->unique();
$table->string('descrizione');
$table->string('tipo_conto', 50)->comment('Es. PATRIMONIALE_ATTIVITA, ECONOMICO_COSTO, FINANZIARIO_ATTIVITA');
$table->string('natura_saldo_tipico', 5)->nullable()->comment('DARE o AVERE');
$table->boolean('is_conto_finanziario')->default(false);
$table->text('note')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('piani_conti_modello');
}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: la creazione della tabella 'piano_conti_condominio' e le relative FK sono ora gestite dalla migration unificata/master.
// Questo file può essere cancellato dopo la bonifica.
return new class extends Illuminate\Database\Migrations\Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration legacy svuotata: la tabella transazioni_contabili è ora gestita nella migration master/unificata.
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration legacy svuotata: la tabella righe_movimenti_contabili è ora gestita nella migration master/unificata.
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,20 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Questa migration è stata svuotata perché la tabella 'preventivi' viene gestita da una migration successiva più completa.
* Non eseguire alcuna operazione qui per evitare errori di tabella già esistente.
*/
public function up(): void
{
// Tabella 'preventivi' già gestita da una migration successiva.
}
public function down(): void
{
// Nessuna operazione di rollback necessaria.
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration legacy svuotata: la tabella contratti_locazione_attiva è ora gestita nella migration master/unificata.
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration legacy svuotata: la tabella scadenze_locazione è ora gestita nella migration master/unificata.
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('audit_logs', function (Blueprint $table) {
$table->bigIncrements('id_audit_log');
$table->unsignedBigInteger('id_utente')->nullable(); // Utente che ha effettuato la modifica
$table->string('nome_tabella', 100);
$table->unsignedBigInteger('id_record_modificato'); // ID del record modificato
$table->string('azione', 50); // INSERT, UPDATE, DELETE
$table->jsonb('valori_precedenti')->nullable(); // Stato prima della modifica
$table->jsonb('valori_nuovi')->nullable(); // Stato dopo la modifica
$table->text('note')->nullable();
$table->timestamps(); // created_at sarà la data_modifica
// Potresti aggiungere una foreign key per id_utente
// $table->foreign('id_utente')->references('id')->on('users')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('audit_logs');
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration patch svuotata: i campi protocollo sono ora gestiti nella migration master/unificata.
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,148 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
throw_if(empty($tableNames), new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'));
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'));
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
// Popola ruoli di base
DB::table('roles')->insertOrIgnore([
['name' => 'super-admin', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'admin', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'collaboratore', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'condomino', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'fornitore', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'inquilino', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'ospite', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
['name' => 'servizi', 'guard_name' => 'web', 'created_at' => now(), 'updated_at' => now()],
]);
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration patch svuotata: la tabella gestioni e i nuovi campi sono ora gestiti nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration legacy svuotata: la tabella richieste_modifiche è ora gestita nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration legacy svuotata: la tabella preventivi è ora gestita nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration legacy svuotata: la tabella bilanci è ora gestita nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration legacy svuotata: la tabella assemblee è ora gestita nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Migration legacy svuotata: le tabelle di rateizzazione sono ora gestite nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: le tabelle sono gestite dalla migration master.
}
};

View File

@ -0,0 +1,89 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Crea la tabella rate_emesse con tutte le foreign key.
*/
public function up(): void
{
if (!Schema::hasTable('rate_emesse')) {
Schema::create('rate_emesse', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('piano_rateizzazione_id');
$table->unsignedBigInteger('unita_immobiliare_id');
$table->unsignedBigInteger('soggetto_responsabile_id');
$table->integer('numero_rata_progressivo');
$table->text('descrizione')->nullable();
$table->decimal('importo_originario_unita', 15, 2);
$table->decimal('percentuale_addebito_soggetto', 7, 4)->default(100.0000);
$table->decimal('importo_addebitato_soggetto', 15, 2);
$table->date('data_emissione');
$table->date('data_scadenza');
$table->string('stato_rata', 50)->default('EMESSA');
$table->date('data_ultimo_pagamento')->nullable();
$table->decimal('importo_pagato', 15, 2)->default(0.00);
$table->text('note')->nullable();
$table->unsignedBigInteger('transazione_contabile_emissione_id')->nullable();
$table->unique(['piano_rateizzazione_id', 'unita_immobiliare_id', 'soggetto_responsabile_id', 'numero_rata_progressivo'], 'unique_rata_per_soggetto_unita');
$table->timestamps();
});
}
// Foreign key su piani_rateizzazione
if (Schema::hasTable('rate_emesse') && Schema::hasTable('piani_rateizzazione')) {
$fkExists = \Illuminate\Support\Facades\DB::select("SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'rate_emesse' AND COLUMN_NAME = 'piano_rateizzazione_id' AND CONSTRAINT_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL");
if (empty($fkExists)) {
Schema::table('rate_emesse', function (Blueprint $table) {
$table->foreign('piano_rateizzazione_id')
->references('id_piano_rateizzazione')
->on('piani_rateizzazione')
->onDelete('cascade');
});
}
}
// Foreign key su unita_immobiliari
if (Schema::hasTable('rate_emesse') && Schema::hasTable('unita_immobiliari')) {
$fkExists = \Illuminate\Support\Facades\DB::select("SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'rate_emesse' AND COLUMN_NAME = 'unita_immobiliare_id' AND CONSTRAINT_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL");
if (empty($fkExists)) {
Schema::table('rate_emesse', function (Blueprint $table) {
$table->foreign('unita_immobiliare_id')
->references('id_unita')
->on('unita_immobiliari')
->onDelete('cascade');
});
}
}
// Foreign key su soggetti
if (Schema::hasTable('rate_emesse') && Schema::hasTable('soggetti')) {
$fkExists = \Illuminate\Support\Facades\DB::select("SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'rate_emesse' AND COLUMN_NAME = 'soggetto_responsabile_id' AND CONSTRAINT_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL");
if (empty($fkExists)) {
Schema::table('rate_emesse', function (Blueprint $table) {
$table->foreign('soggetto_responsabile_id')
->references('id_soggetto')
->on('soggetti')
->onDelete('restrict');
});
}
}
// Foreign key su transazioni_contabili
if (Schema::hasTable('rate_emesse') && Schema::hasTable('transazioni_contabili')) {
$fkExists = \Illuminate\Support\Facades\DB::select("SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'rate_emesse' AND COLUMN_NAME = 'transazione_contabile_emissione_id' AND CONSTRAINT_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL");
if (empty($fkExists)) {
Schema::table('rate_emesse', function (Blueprint $table) {
$table->foreign('transazione_contabile_emissione_id')
->references('id_transazione')
->on('transazioni_contabili')
->onDelete('set null');
});
}
}
}
public function down(): void
{
Schema::dropIfExists('rate_emesse');
}
};

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Migration patch svuotata: la tabella rate_emesse e le FK sono ora gestite nella migration master/unificata.
}
public function down(): void
{
// Nessuna azione: la tabella viene gestita dalla migration master.
}
};

View File

@ -0,0 +1,179 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Crea tutte le tabelle anagrafiche principali e le relative foreign key.
* Ordine di creazione: amministratori -> fornitori -> soggetti -> stabili -> unita_immobiliari
*/
public function up(): void
{
// --- Amministratori ---
Schema::create('amministratori', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('cognome');
$table->unsignedBigInteger('user_id');
$table->string('denominazione_studio')->nullable();
$table->string('partita_iva')->nullable()->unique();
$table->string('codice_fiscale_studio')->nullable();
$table->string('indirizzo_studio')->nullable();
$table->string('cap_studio', 10)->nullable();
$table->string('citta_studio', 60)->nullable();
$table->string('provincia_studio', 2)->nullable();
$table->string('telefono_studio')->nullable();
$table->string('email_studio')->nullable();
$table->string('pec_studio')->nullable();
$table->string('codice_univoco', 8)->unique();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
// --- Fornitori ---
Schema::create('fornitori', function (Blueprint $table) {
$table->id();
$table->integer('old_id')->nullable()->unique()->comment('ID dal vecchio gestionale');
$table->unsignedBigInteger('amministratore_id');
$table->string('ragione_sociale');
$table->string('partita_iva', 20)->nullable();
$table->string('codice_fiscale', 20)->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('citta', 60)->nullable();
$table->string('provincia', 2)->nullable();
$table->string('email')->nullable();
$table->string('pec')->nullable();
$table->string('telefono')->nullable();
$table->timestamps();
$table->foreign('amministratore_id')->references('id')->on('amministratori')->onDelete('cascade');
});
// --- Soggetti ---
Schema::create('soggetti', function (Blueprint $table) {
$table->id();
$table->integer('old_id')->nullable()->unique()->comment('ID dal vecchio gestionale');
$table->string('nome')->nullable();
$table->string('cognome')->nullable();
$table->string('ragione_sociale')->nullable();
$table->string('codice_fiscale', 16)->nullable()->index();
$table->string('partita_iva', 11)->nullable()->index();
$table->string('email')->nullable()->index();
$table->string('telefono')->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('citta', 60)->nullable();
$table->string('provincia', 2)->nullable();
$table->enum('tipo', ['proprietario', 'inquilino', 'usufruttuario', 'altro']);
$table->string('codice_univoco', 8)->unique();
$table->timestamps();
});
// --- Stabili ---
Schema::create('stabili', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('amministratore_id');
$table->string('denominazione');
$table->string('indirizzo');
$table->string('cap', 10);
$table->string('citta', 60);
$table->string('provincia', 2);
$table->string('codice_fiscale', 20)->nullable()->unique();
$table->text('note')->nullable();
$table->json('rate_ordinarie_mesi')->nullable();
$table->json('rate_riscaldamento_mesi')->nullable();
$table->text('descrizione_rate')->nullable();
$table->string('stato', 50)->default('attivo');
$table->integer('old_id')->nullable()->unique();
$table->timestamps();
$table->softDeletes();
$table->foreign('amministratore_id')->references('id')->on('amministratori')->onDelete('cascade');
});
// --- Piano Conti Condominio ---
Schema::create('piano_conti_condominio', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('stabile_id');
$table->string('codice', 20);
$table->string('descrizione');
$table->string('tipo_conto', 20)->nullable();
$table->boolean('attivo')->default(true);
$table->timestamps();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
$table->unique(['stabile_id', 'codice'], 'unique_conto_per_stabile');
});
// --- Unita Immobiliari ---
Schema::create('unita_immobiliari', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('stabile_id');
$table->string('fabbricato')->nullable();
$table->string('interno')->nullable();
$table->string('scala')->nullable();
$table->string('piano')->nullable();
$table->string('subalterno')->nullable();
$table->string('categoria_catastale', 10)->nullable();
$table->decimal('superficie', 8, 2)->nullable();
$table->decimal('vani', 5, 2)->nullable();
$table->string('indirizzo')->nullable()->comment('Indirizzo specifico se diverso da quello del condominio');
$table->text('note')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
});
// --- Proprieta ---
Schema::create('proprieta', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('soggetto_id');
$table->unsignedBigInteger('unita_immobiliare_id');
$table->string('tipo_diritto', 50)->nullable();
$table->decimal('percentuale_possesso', 7, 4)->nullable();
$table->date('data_inizio')->nullable();
$table->date('data_fine')->nullable();
$table->timestamps();
$table->foreign('soggetto_id')->references('id')->on('soggetti')->onDelete('cascade');
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('cascade');
$table->unique(['soggetto_id', 'unita_immobiliare_id', 'tipo_diritto'], 'unique_proprieta_per_unita_soggetto');
});
// --- Tabelle Millesimali ---
Schema::create('tabelle_millesimali', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('stabile_id');
$table->string('nome_tabella_millesimale');
$table->text('descrizione')->nullable();
$table->timestamps();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
$table->unique(['stabile_id', 'nome_tabella_millesimale'], 'unique_tabella_per_stabile');
});
// --- Dettagli Tabelle Millesimali ---
Schema::create('dettagli_tabelle_millesimali', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tabella_millesimale_id');
$table->unsignedBigInteger('unita_immobiliare_id');
$table->decimal('millesimi', 10, 4);
$table->timestamps();
$table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('cascade');
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('cascade');
$table->unique(['tabella_millesimale_id', 'unita_immobiliare_id'], 'unique_tabella_unita');
});
}
public function down(): void
{
Schema::dropIfExists('dettagli_tabelle_millesimali');
Schema::dropIfExists('tabelle_millesimali');
Schema::dropIfExists('proprieta');
Schema::dropIfExists('unita_immobiliari');
Schema::dropIfExists('stabili');
Schema::dropIfExists('soggetti');
Schema::dropIfExists('fornitori');
Schema::dropIfExists('amministratori');
Schema::dropIfExists('piano_conti_condominio');
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Crea la tabella allegati per la gestione degli allegati generici (morphable).
*/
public function up(): void
{
Schema::create('allegati', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('stabile_id')->nullable();
$table->string('nome_file_originale');
$table->string('nome_file_storage')->unique();
$table->text('percorso_file_storage');
$table->string('tipo_mime', 100);
$table->bigInteger('dimensione_byte')->unsigned();
$table->text('descrizione')->nullable();
$table->unsignedBigInteger('allegabile_id');
$table->string('allegabile_type', 100);
$table->unsignedBigInteger('id_utente_caricamento')->nullable();
$table->string('tags')->nullable();
$table->index(['allegabile_id', 'allegabile_type']);
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('set null');
// $table->foreign('id_utente_caricamento')->references('id')->on('users')->onDelete('set null');
});
}
public function down(): void
{
Schema::dropIfExists('allegati');
}
};

View File

@ -0,0 +1,100 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Crea tutte le tabelle e relazioni per la gestione dei ticket (categorie, ticket, aggiornamenti, messaggi, allegati).
*/
public function up(): void
{
// --- Categorie Ticket ---
Schema::create('categorie_ticket', function (Blueprint $table) {
$table->id();
$table->string('nome')->unique();
$table->text('descrizione')->nullable();
$table->timestamps();
});
// --- Tickets ---
Schema::create('tickets', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('stabile_id');
$table->unsignedBigInteger('unita_immobiliare_id')->nullable();
$table->unsignedBigInteger('soggetto_richiedente_id')->nullable();
$table->unsignedBigInteger('aperto_da_user_id');
$table->unsignedBigInteger('categoria_ticket_id')->nullable();
$table->string('titolo');
$table->text('descrizione');
$table->string('luogo_intervento')->nullable()->comment('Es. Scala A, Piano 3, Interno 5');
$table->enum('stato', [
'Aperto', 'Preso in Carico', 'In Lavorazione', 'In Attesa Approvazione',
'In Attesa Ricambi', 'Risolto', 'Chiuso', 'Annullato'
])->default('Aperto');
$table->enum('priorita', ['Bassa', 'Media', 'Alta', 'Urgente'])->default('Media');
$table->unsignedBigInteger('assegnato_a_user_id')->nullable();
$table->unsignedBigInteger('assegnato_a_fornitore_id')->nullable();
$table->timestamp('data_apertura')->useCurrent();
$table->date('data_scadenza_prevista')->nullable();
$table->timestamp('data_risoluzione_effettiva')->nullable();
$table->timestamp('data_chiusura_effettiva')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('set null');
$table->foreign('soggetto_richiedente_id')->references('id')->on('soggetti')->onDelete('set null');
$table->foreign('aperto_da_user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('categoria_ticket_id')->references('id')->on('categorie_ticket')->onDelete('set null');
$table->foreign('assegnato_a_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('assegnato_a_fornitore_id')->references('id')->on('fornitori')->onDelete('set null');
});
// --- Ticket Updates ---
Schema::create('ticket_updates', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('ticket_id');
$table->unsignedBigInteger('user_id')->nullable();
$table->text('update_text')->comment("Testo dell'aggiornamento o nota interna");
$table->timestamps();
$table->foreign('ticket_id')->references('id')->on('tickets')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
});
// --- Ticket Messages ---
Schema::create('ticket_messages', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('ticket_id');
$table->unsignedBigInteger('user_id')->nullable();
$table->text('messaggio');
$table->timestamps();
$table->foreign('ticket_id')->references('id')->on('tickets')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
});
// --- Ticket Attachments ---
Schema::create('ticket_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained('tickets')->onDelete('cascade');
$table->foreignId('ticket_update_id')->nullable()->constrained('ticket_updates')->onDelete('cascade');
$table->foreignId('user_id')->comment('Utente che ha caricato l allegato')->constrained('users')->onDelete('cascade');
$table->string('file_path');
$table->string('original_file_name');
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('size')->nullable()->comment('Dimensione in bytes');
$table->string('description')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ticket_attachments');
Schema::dropIfExists('ticket_messages');
Schema::dropIfExists('ticket_updates');
Schema::dropIfExists('tickets');
Schema::dropIfExists('categorie_ticket');
}
};

View File

@ -0,0 +1,9 @@
<?php
// Migration svuotata: duplicata e non più necessaria, gestione ruoli/permessi demandata a Spatie/Permission.
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
public function up(): void {}
public function down(): void {}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('documenti', function (Blueprint $table) {
$table->id();
$table->string('protocollo')->nullable()->unique(); // es. 2025-0001
$table->date('data_protocollo')->nullable();
$table->string('tipo_documento')->nullable(); // Fattura, Contratto, Verbale, ecc.
$table->unsignedBigInteger('stabile_id')->nullable();
$table->unsignedBigInteger('fornitore_id')->nullable();
$table->unsignedBigInteger('esercizio_contabile_id')->nullable();
$table->text('descrizione')->nullable();
$table->decimal('importo', 12, 2)->nullable();
$table->date('data_documento')->nullable();
$table->string('nome_file');
$table->string('path_file');
$table->text('testo_estratto_ocr')->nullable();
// Polimorfismo
$table->unsignedBigInteger('documentable_id')->nullable()->index();
$table->string('documentable_type')->nullable()->index();
$table->timestamps();
// FK opzionali
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('set null');
$table->foreign('fornitore_id')->references('id')->on('fornitori')->onDelete('set null');
// $table->foreign('esercizio_contabile_id')->references('id')->on('esercizi_contabili')->onDelete('set null'); // FK disabilitata: tabella non ancora presente
});
}
public function down(): void
{
Schema::dropIfExists('documenti');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('gestioni', function (Blueprint $table) {
$table->id('id_gestione');
$table->unsignedBigInteger('stabile_id');
$table->year('anno_gestione');
$table->string('tipo_gestione', 20)->default('Ord.'); // Ord., Risc., Straord.
$table->date('data_inizio')->nullable();
$table->date('data_fine')->nullable();
$table->enum('stato', ['aperta', 'in_corso', 'chiusa'])->default('aperta');
$table->text('descrizione')->nullable();
$table->timestamps();
$table->softDeletes();
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
$table->index(['stabile_id', 'anno_gestione']);
});
}
public function down(): void
{
Schema::dropIfExists('gestioni');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_settings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('key');
$table->text('value')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unique(['user_id', 'key']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_settings');
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('impostazioni', function (Blueprint $table) {
$table->id();
$table->string('chiave')->unique();
$table->string('valore')->nullable();
$table->string('descrizione')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('impostazioni');
}
};

View File

@ -0,0 +1,105 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('movimenti_contabili', function (Blueprint $table) {
// Aggiunge colonne mancanti per la nuova struttura
if (!Schema::hasColumn('movimenti_contabili', 'codice_movimento')) {
$table->char('codice_movimento', 8)->unique()->after('id')->comment('Codice alfanumerico univoco 8 caratteri');
}
if (!Schema::hasColumn('movimenti_contabili', 'stato_movimento')) {
$table->enum('stato_movimento', ['prima_nota', 'bozza', 'confermato', 'chiuso'])->default('prima_nota')->after('documento_id');
}
if (!Schema::hasColumn('movimenti_contabili', 'data_prima_nota')) {
$table->timestamp('data_prima_nota')->nullable()->after('stato_movimento');
}
if (!Schema::hasColumn('movimenti_contabili', 'data_conferma')) {
$table->timestamp('data_conferma')->nullable()->after('data_prima_nota');
}
if (!Schema::hasColumn('movimenti_contabili', 'confermato_da')) {
$table->unsignedBigInteger('confermato_da')->nullable()->after('data_conferma');
}
if (!Schema::hasColumn('movimenti_contabili', 'categoria_movimento')) {
$table->enum('categoria_movimento', ['ordinario', 'straordinario', 'fondo', 'lavori'])->default('ordinario')->after('tipo_movimento');
}
if (!Schema::hasColumn('movimenti_contabili', 'iva')) {
$table->decimal('iva', 10, 2)->default(0)->after('ritenuta_acconto');
}
if (!Schema::hasColumn('movimenti_contabili', 'dettagli_partita_doppia')) {
$table->json('dettagli_partita_doppia')->nullable()->after('importo_netto')->comment('Struttura per dare/avere futuro');
}
if (!Schema::hasColumn('movimenti_contabili', 'note_interne')) {
$table->text('note_interne')->nullable()->after('dettagli_partita_doppia');
}
if (!Schema::hasColumn('movimenti_contabili', 'creato_da')) {
$table->unsignedBigInteger('creato_da')->nullable()->after('note_interne');
}
if (!Schema::hasColumn('movimenti_contabili', 'modificato_da')) {
$table->unsignedBigInteger('modificato_da')->nullable()->after('creato_da');
}
});
// Aggiunge foreign keys dopo aver creato le colonne
Schema::table('movimenti_contabili', function (Blueprint $table) {
// Foreign keys solo se non esistono già
if (Schema::hasColumn('movimenti_contabili', 'confermato_da')) {
$table->foreign('confermato_da')->references('id')->on('users')->onDelete('set null');
}
if (Schema::hasColumn('movimenti_contabili', 'creato_da')) {
$table->foreign('creato_da')->references('id')->on('users')->onDelete('set null');
}
if (Schema::hasColumn('movimenti_contabili', 'modificato_da')) {
$table->foreign('modificato_da')->references('id')->on('users')->onDelete('set null');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('movimenti_contabili', function (Blueprint $table) {
// Rimuove foreign keys
$table->dropForeign(['confermato_da']);
$table->dropForeign(['creato_da']);
$table->dropForeign(['modificato_da']);
// Rimuove colonne aggiunte
$table->dropColumn([
'codice_movimento',
'stato_movimento',
'data_prima_nota',
'data_conferma',
'confermato_da',
'categoria_movimento',
'iva',
'dettagli_partita_doppia',
'note_interne',
'creato_da',
'modificato_da'
]);
});
}
};

View File

@ -0,0 +1,76 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Aggiorna la struttura della tabella allegati per renderla completamente
* conforme alle best practice Laravel.
*/
public function up(): void
{
Schema::table('allegati', function (Blueprint $table) {
// Rinomina id_utente_caricamento in user_id per seguire lo standard Laravel
if (Schema::hasColumn('allegati', 'id_utente_caricamento')) {
$table->renameColumn('id_utente_caricamento', 'user_id');
}
// Aggiungi timestamps se non esistono
if (!Schema::hasColumn('allegati', 'created_at')) {
$table->timestamps();
}
// Aggiungi soft deletes per gestire cancellazioni logiche
if (!Schema::hasColumn('allegati', 'deleted_at')) {
$table->softDeletes();
}
// Aggiungi codice alfanumerico unico per identificazione
if (!Schema::hasColumn('allegati', 'codice_allegato')) {
$table->string('codice_allegato', 8)->unique()->after('id');
$table->index('codice_allegato');
}
// Aggiungi foreign key per user_id (solo se la colonna esiste e non ha già la FK)
try {
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
} catch (\Exception $e) {
// Foreign key potrebbe già esistere, ignora l'errore
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('allegati', function (Blueprint $table) {
// Rimuovi foreign key se esiste
try {
$table->dropForeign(['user_id']);
} catch (\Exception $e) {
// Foreign key potrebbe non esistere, ignora l'errore
}
// Rimuovi le colonne aggiunte se esistono
if (Schema::hasColumn('allegati', 'codice_allegato')) {
$table->dropColumn('codice_allegato');
}
if (Schema::hasColumn('allegati', 'deleted_at')) {
$table->dropColumn('deleted_at');
}
if (Schema::hasColumn('allegati', 'created_at')) {
$table->dropTimestamps();
}
// Ripristina il nome originale della colonna se è stata rinominata
if (Schema::hasColumn('allegati', 'user_id')) {
$table->renameColumn('user_id', 'id_utente_caricamento');
}
});
}
};

View File

@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Crea la tabella amministratori moderna con best practice Laravel
* e supporto per sistema multi-database.
*/
public function up(): void
{
Schema::create('amministratori', function (Blueprint $table) {
$table->id(); // Chiave primaria standard Laravel
// Codice alfanumerico unico di 8 caratteri per identificazione
$table->string('codice_amministratore', 8)->unique();
$table->index('codice_amministratore');
// Dati amministratore
$table->string('ragione_sociale');
$table->string('nome')->nullable();
$table->string('cognome')->nullable();
$table->string('codice_fiscale', 16)->unique();
$table->string('partita_iva', 11)->nullable();
// Contatti
$table->string('indirizzo')->nullable();
$table->string('cap', 5)->nullable();
$table->string('citta')->nullable();
$table->string('provincia', 2)->nullable();
$table->string('telefono')->nullable();
$table->string('cellulare')->nullable();
$table->string('email')->unique();
$table->string('pec')->nullable();
$table->string('sito_web')->nullable();
// Sistema multi-database e cartelle
$table->string('database_name', 8)->nullable(); // Nome DB dedicato (stesso del codice)
$table->string('cartella_dati', 255)->nullable(); // Percorso cartella dati
$table->boolean('database_attivo')->default(false); // Se ha DB separato
$table->string('server_database')->nullable(); // Server hosting DB (per distribuzione)
// Configurazioni
$table->json('configurazioni')->nullable(); // JSON per impostazioni personalizzate
$table->decimal('commissione_percentuale', 5, 2)->default(0.00);
$table->decimal('costo_fisso_mensile', 8, 2)->default(0.00);
// Stato e date
$table->enum('stato', ['attivo', 'sospeso', 'disattivato'])->default('attivo');
$table->date('data_inizio_attivita')->nullable();
$table->date('data_scadenza_contratto')->nullable();
// Laravel standard
$table->timestamps();
$table->softDeletes();
// Indici per performance
$table->index(['stato', 'database_attivo']);
$table->index('codice_fiscale');
$table->index('email');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('amministratori');
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('amministratori', function (Blueprint $table) {
$table->softDeletes();
// Aggiungiamo anche il campo codice se non esiste
if (!Schema::hasColumn('amministratori', 'codice')) {
$table->string('codice', 8)->unique()->after('id');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('amministratori', function (Blueprint $table) {
$table->dropSoftDeletes();
if (Schema::hasColumn('amministratori', 'codice')) {
$table->dropColumn('codice');
}
});
}
};

View File

@ -0,0 +1,44 @@
FROM php:8.2-fpm
# Installa dipendenze di sistema
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
nodejs \
npm
# Pulisce cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Installa estensioni PHP
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Installa Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Imposta directory di lavoro
WORKDIR /var/www
# Copia file applicazione
COPY . /var/www
# Installa dipendenze PHP
RUN composer install --optimize-autoloader --no-dev
# Installa dipendenze Node.js
RUN npm install && npm run build
# Imposta permessi
RUN chown -R www-data:www-data /var/www \
&& chmod -R 755 /var/www/storage \
&& chmod -R 755 /var/www/bootstrap/cache
# Espone porta 9000
EXPOSE 9000
CMD ["php-fpm"]

View File

@ -0,0 +1,206 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Amministratore;
use App\Models\Stabile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Spatie\Permission\Models\Role;
class CreateAmministratore extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'netgescon:create-amministratore
{nome : Nome dell\'amministratore}
{cognome : Cognome dell\'amministratore}
{email : Email dell\'amministratore}
{--password= : Password (se non specificata, viene generata)}
{--studio= : Nome dello studio}
{--piva= : Partita IVA}
{--cf= : Codice fiscale}
{--telefono= : Telefono}
{--indirizzo= : Indirizzo}
{--cap= : CAP}
{--citta= : Città}
{--provincia= : Provincia}
{--pec= : Email PEC}
{--multi-db : Abilita database dedicato}';
/**
* The console command description.
*/
protected $description = 'Crea un nuovo amministratore con cartelle e permessi';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('🏗️ Creazione nuovo amministratore...');
// Raccolta dati
$nome = $this->argument('nome');
$cognome = $this->argument('cognome');
$email = $this->argument('email');
$password = $this->option('password') ?: $this->generatePassword();
// Verifica email univoca
if (User::where('email', $email)->exists()) {
$this->error("❌ Email {$email} già esistente!");
return 1;
}
try {
// 1. Crea utente
$this->info("👤 Creazione utente {$nome} {$cognome}...");
$user = User::create([
'name' => "{$nome} {$cognome}",
'email' => $email,
'password' => Hash::make($password),
'email_verified_at' => now(),
]);
// 2. Assegna ruolo amministratore
$user->assignRole('amministratore');
$this->info("✅ Ruolo 'amministratore' assegnato");
// 3. Crea record amministratore
$this->info("🏢 Creazione record amministratore...");
$amministratore = Amministratore::create([
'user_id' => $user->id,
'nome' => $nome,
'cognome' => $cognome,
'denominazione_studio' => $this->option('studio') ?: "Studio {$cognome}",
'partita_iva' => $this->option('piva') ?: null,
'codice_fiscale_studio' => $this->option('cf') ?: null,
'telefono_studio' => $this->option('telefono') ?: null,
'indirizzo_studio' => $this->option('indirizzo') ?: null,
'cap_studio' => $this->option('cap') ?: null,
'citta_studio' => $this->option('citta') ?: null,
'provincia_studio' => $this->option('provincia') ?: null,
'email_studio' => $email,
'pec_studio' => $this->option('pec') ?: null,
]);
// 4. Crea struttura cartelle
$this->info("📁 Creazione cartelle dati...");
$this->createAdminFolders($amministratore);
// 5. Crea stabile di esempio (opzionale)
if ($this->confirm('Vuoi creare uno stabile di esempio?', true)) {
$this->createExampleStabile($amministratore);
}
// 6. Sistema multi-database (se richiesto)
if ($this->option('multi-db')) {
$this->info("🗄️ Configurazione database dedicato...");
$this->setupDedicatedDatabase($amministratore);
}
// Output finale
$this->info('');
$this->info('🎉 Amministratore creato con successo!');
$this->table(['Campo', 'Valore'], [
['Nome Completo', $amministratore->nome_completo],
['Email', $user->email],
['Password', $password],
['Codice Univoco', $amministratore->codice_univoco],
['Studio', $amministratore->denominazione_studio],
['Cartelle', "storage/app/amministratori/{$amministratore->codice_univoco}/"],
['Multi-DB', $this->option('multi-db') ? 'Abilitato' : 'Condiviso'],
]);
$this->warn('⚠️ IMPORTANTE: Salva la password generata!');
return 0;
} catch (\Exception $e) {
$this->error("❌ Errore durante la creazione: " . $e->getMessage());
return 1;
}
}
/**
* Genera password sicura
*/
private function generatePassword(): string
{
return 'Admin' . rand(1000, 9999) . '!';
}
/**
* Crea struttura cartelle per amministratore
*/
private function createAdminFolders(Amministratore $amministratore): void
{
$basePath = "amministratori/{$amministratore->codice_univoco}";
$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}");
$this->line(" 📂 {$folder}");
}
// Crea file README
$readme = "# Cartella Amministratore: {$amministratore->nome_completo}\n\n";
$readme .= "**Codice**: {$amministratore->codice_univoco}\n";
$readme .= "**Creato**: " . now()->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);
}
/**
* Crea stabile di esempio
*/
private function createExampleStabile(Amministratore $amministratore): void
{
$denominazione = $this->ask('Nome del condominio', "Condominio {$amministratore->cognome}");
$stabile = Stabile::create([
'amministratore_id' => $amministratore->id,
'denominazione' => $denominazione,
'indirizzo' => $this->ask('Indirizzo', 'Via Roma 1'),
'cap' => $this->ask('CAP', '00100'),
'citta' => $this->ask('Città', 'Roma'),
'provincia' => $this->ask('Provincia', 'RM'),
'codice_fiscale' => strtoupper(substr($denominazione, 0, 3)) . rand(100000, 999999),
'stato' => 'attivo',
]);
$this->info("🏢 Stabile creato: {$stabile->denominazione}");
}
/**
* Configura database dedicato (placeholder)
*/
private function setupDedicatedDatabase(Amministratore $amministratore): void
{
// TODO: Implementare logica multi-database
$dbName = "netgescon_" . strtolower($amministratore->codice_univoco);
$this->line(" 🗄️ Database: {$dbName}");
$this->warn(" ⚠️ Implementazione multi-DB in development");
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace App\Console\Commands;
use App\Models\Amministratore;
use App\Services\DistributionService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class ManageDistribution extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'distribution:manage
{action : Action to perform (migrate|status|backup|test)}
{--administrator= : Administrator code (8 characters)}
{--target-server= : Target server URL for migration}
{--all : Apply to all administrators}';
/**
* The console command description.
*/
protected $description = 'Manage multi-server distribution of administrators';
/**
* Execute the console command.
*/
public function handle()
{
$action = $this->argument('action');
switch ($action) {
case 'migrate':
return $this->migrateAdministrator();
case 'status':
return $this->showDistributionStatus();
case 'backup':
return $this->backupAdministrator();
case 'test':
return $this->testDistribution();
default:
$this->error("Unknown action: {$action}");
return 1;
}
}
/**
* Migra un amministratore verso un altro server
*/
private function migrateAdministrator()
{
$adminCode = $this->option('administrator');
$targetServer = $this->option('target-server');
if (!$adminCode) {
$adminCode = $this->ask('Administrator code (8 characters)');
}
if (!$targetServer) {
$targetServer = $this->ask('Target server URL');
}
if (!$adminCode || !$targetServer) {
$this->error('Both administrator code and target server are required');
return 1;
}
$amministratore = Amministratore::where('codice_amministratore', $adminCode)->first();
if (!$amministratore) {
$this->error("Administrator {$adminCode} not found");
return 1;
}
$this->info("Starting migration of administrator {$adminCode} to {$targetServer}");
// Verifica server target
$this->info('Checking target server health...');
$healthCheck = DistributionService::checkServerHealth($targetServer);
if (!$healthCheck['success']) {
$this->error("Target server health check failed: {$healthCheck['error']}");
return 1;
}
$this->info('Target server is healthy and compatible');
// Conferma migrazione
if (!$this->confirm("Are you sure you want to migrate administrator {$adminCode} to {$targetServer}?")) {
$this->info('Migration cancelled');
return 0;
}
// Esegui migrazione
$this->info('Starting migration process...');
$result = DistributionService::migrateAdministrator($amministratore, $targetServer);
if ($result['success']) {
$this->info("✅ Migration completed successfully!");
$this->info("Administrator {$adminCode} is now accessible at: {$result['new_url']}");
} else {
$this->error("❌ Migration failed: {$result['error']}");
return 1;
}
return 0;
}
/**
* Mostra stato della distribuzione
*/
private function showDistributionStatus()
{
$this->info('NetGesCon Multi-Server Distribution Status');
$this->info('==========================================');
$stats = DistributionService::getDistributionStats();
$this->info("Total Administrators: {$stats['total_administrators']}");
$this->info("Local Administrators: {$stats['local_administrators']}");
$this->info("Distributed Administrators: {$stats['distributed_administrators']}");
$this->newLine();
$this->info('Server Distribution:');
if (empty($stats['servers'])) {
$this->info(' No distributed servers configured');
} else {
foreach ($stats['servers'] as $server) {
$this->info(" {$server['server']}: {$server['administrators_count']} administrators");
}
}
$this->newLine();
$this->info('Status Distribution:');
foreach ($stats['status_distribution'] as $status => $count) {
$this->info(" {$status}: {$count}");
}
// Mostra dettagli amministratori distribuiti
$distributedAdmins = Amministratore::whereNotNull('server_database')->get();
if ($distributedAdmins->isNotEmpty()) {
$this->newLine();
$this->info('Distributed Administrators Details:');
$this->table(
['Code', 'Name', 'Server', 'Status', 'Last Backup'],
$distributedAdmins->map(function ($admin) {
return [
$admin->codice_amministratore,
$admin->nome_completo,
$admin->server_database ?: 'localhost',
$admin->stato_sincronizzazione,
$admin->ultimo_backup ? $admin->ultimo_backup->format('Y-m-d H:i') : 'Never'
];
})
);
}
return 0;
}
/**
* Esegue backup di un amministratore
*/
private function backupAdministrator()
{
$adminCode = $this->option('administrator');
$all = $this->option('all');
if (!$adminCode && !$all) {
$adminCode = $this->ask('Administrator code (8 characters) or use --all for all administrators');
}
if ($all) {
$this->info('Backing up all administrators...');
$administrators = Amministratore::all();
} else {
$administrators = Amministratore::where('codice_amministratore', $adminCode)->get();
if ($administrators->isEmpty()) {
$this->error("Administrator {$adminCode} not found");
return 1;
}
}
$successCount = 0;
$errorCount = 0;
foreach ($administrators as $admin) {
$this->info("Backing up administrator {$admin->codice_amministratore}...");
$result = DistributionService::performDistributedBackup($admin);
if ($result['success']) {
$this->info("✅ Backup completed for {$admin->codice_amministratore}");
$successCount++;
} else {
$this->error("❌ Backup failed for {$admin->codice_amministratore}: {$result['error']}");
$errorCount++;
}
}
$this->info("Backup completed: {$successCount} successful, {$errorCount} errors");
return $errorCount > 0 ? 1 : 0;
}
/**
* Testa la funzionalità di distribuzione
*/
private function testDistribution()
{
$this->info('Testing distribution functionality...');
// Test 1: Verifica struttura cartelle
$this->info('1. Testing folder structure...');
$testAdmin = Amministratore::first();
if (!$testAdmin) {
$this->error('No administrators found for testing');
return 1;
}
$archiveInfo = $testAdmin->getArchiveInfo();
if ($archiveInfo['exists']) {
$this->info("✅ Archive exists: {$archiveInfo['path']}");
$this->info(" Size: {$archiveInfo['size_formatted']}");
$this->info(" Files: {$archiveInfo['files_count']}");
} else {
$this->warn("⚠️ Archive not found for {$testAdmin->codice_amministratore}");
}
// Test 2: Health check locale
$this->info('2. Testing local health check...');
$localUrl = config('app.url');
$healthCheck = DistributionService::checkServerHealth($localUrl);
if ($healthCheck['success']) {
$this->info("✅ Local server health check passed");
} else {
$this->error("❌ Local server health check failed: {$healthCheck['error']}");
}
// Test 3: Backup test
$this->info('3. Testing backup functionality...');
$backupResult = $testAdmin->createDatabaseBackup();
if ($backupResult['success']) {
$this->info("✅ Backup test passed");
$this->info(" File: {$backupResult['filename']}");
$this->info(" Size: " . number_format($backupResult['size']) . " bytes");
} else {
$this->error("❌ Backup test failed: {$backupResult['error']}");
}
// Test 4: Migrazione simulata
$this->info('4. Testing migration preparation...');
$migrationResult = $testAdmin->prepareForMigration();
if ($migrationResult['success']) {
$this->info("✅ Migration preparation test passed");
$this->info(" Archive: {$migrationResult['zip_file']}");
$this->info(" Size: " . number_format($migrationResult['size']) . " bytes");
// Cleanup test files
if (file_exists($migrationResult['zip_file'])) {
unlink($migrationResult['zip_file']);
}
if (file_exists($migrationResult['metadata_file'])) {
unlink($migrationResult['metadata_file']);
}
} else {
$this->error("❌ Migration preparation test failed: {$migrationResult['error']}");
}
$this->info('Distribution testing completed');
return 0;
}
}

View File

@ -0,0 +1,317 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Stabile;
use App\Models\Soggetto;
use App\Models\Ticket;
use App\Models\UnitaImmobiliare;
use App\Models\Rata;
use App\Models\Assemblea;
use App\Models\User;
use Carbon\Carbon;
class PopulateTestData extends Command
{
protected $signature = 'netgescon:populate-test-data';
protected $description = 'Popola il database con dati di test per NetGesCon';
public function handle()
{
$this->info('🏢 Popolamento dati di test NetGesCon...');
// Crea stabili di test
$this->createTestStabili();
// Crea condomini di test
$this->createTestCondomini();
// Crea tickets di test
$this->createTestTickets();
// Crea rate di test
$this->createTestRate();
// Crea assemblee di test
$this->createTestAssemblee();
$this->info('✅ Dati di test creati con successo!');
$this->info('📊 Usa /test-sidebar-data per vedere il risultato');
}
private function createTestStabili()
{
if (Stabile::count() > 0) {
$this->info('⏭️ Stabili già presenti, skip...');
return;
}
$stabili = [
[
'nome' => 'Condominio Roma Centro',
'indirizzo' => 'Via Roma 123, Roma',
'codice_fiscale' => 'STABROM123',
'stato' => 'attivo',
'created_at' => now(),
'updated_at' => now(),
],
[
'nome' => 'Residenza Milano Nord',
'indirizzo' => 'Via Milano 456, Milano',
'codice_fiscale' => 'STABMIL456',
'stato' => 'attivo',
'created_at' => now(),
'updated_at' => now(),
],
[
'nome' => 'Condominio Firenze Sud',
'indirizzo' => 'Via Firenze 789, Firenze',
'codice_fiscale' => 'STABFIR789',
'stato' => 'inattivo',
'created_at' => now(),
'updated_at' => now(),
]
];
foreach ($stabili as $stabile) {
Stabile::create($stabile);
}
$this->info('🏢 Creati 3 stabili di test');
}
private function createTestCondomini()
{
if (Soggetto::count() > 0) {
$this->info('⏭️ Condomini già presenti, skip...');
return;
}
$condomini = [
[
'nome' => 'Mario',
'cognome' => 'Rossi',
'codice_fiscale' => 'RSSMRA80A01H501Z',
'tipo' => 'proprietario',
'email' => 'mario.rossi@example.com',
'telefono' => '333-1234567',
'created_at' => now(),
'updated_at' => now(),
],
[
'nome' => 'Giulia',
'cognome' => 'Bianchi',
'codice_fiscale' => 'BNCGLI85B15H501Y',
'tipo' => 'proprietario',
'email' => 'giulia.bianchi@example.com',
'telefono' => '333-2345678',
'created_at' => now(),
'updated_at' => now(),
],
[
'nome' => 'Luca',
'cognome' => 'Verdi',
'codice_fiscale' => 'VRDLCU90C20H501X',
'tipo' => 'inquilino',
'email' => 'luca.verdi@example.com',
'telefono' => '333-3456789',
'created_at' => now(),
'updated_at' => now(),
],
[
'nome' => 'Anna',
'cognome' => 'Neri',
'codice_fiscale' => 'NRANNA75D25H501W',
'tipo' => 'inquilino',
'email' => 'anna.neri@example.com',
'telefono' => '333-4567890',
'created_at' => now(),
'updated_at' => now(),
]
];
foreach ($condomini as $condomino) {
Soggetto::create($condomino);
}
$this->info('👥 Creati 4 condomini di test');
}
private function createTestTickets()
{
if (Ticket::count() > 0) {
$this->info('⏭️ Tickets già presenti, skip...');
return;
}
// Prendi il primo stabile disponibile
$stabile = Stabile::first();
if (!$stabile) {
$this->error('❌ Nessuno stabile trovato. Crea prima uno stabile.');
return;
}
// Prendi il primo utente disponibile
$user = User::first();
if (!$user) {
$this->error('❌ Nessun utente trovato. Crea prima un utente.');
return;
}
$tickets = [
[
'stabile_id' => $stabile->id,
'aperto_da_user_id' => $user->id,
'titolo' => 'Perdita d\'acqua nel bagno',
'descrizione' => 'C\'è una perdita d\'acqua nel bagno del primo piano',
'priorita' => 'Alta',
'stato' => 'Aperto',
'data_apertura' => now(),
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'aperto_da_user_id' => $user->id,
'titolo' => 'Problema ascensore',
'descrizione' => 'L\'ascensore si ferma tra il secondo e il terzo piano',
'priorita' => 'Alta',
'stato' => 'In Lavorazione',
'data_apertura' => now(),
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'aperto_da_user_id' => $user->id,
'titolo' => 'Richiesta sostituzione lampadina',
'descrizione' => 'La lampadina nell\'androne è bruciata',
'priorita' => 'Bassa',
'stato' => 'Aperto',
'data_apertura' => now(),
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'aperto_da_user_id' => $user->id,
'titolo' => 'Rumore eccessivo',
'descrizione' => 'Il vicino del piano di sopra fa troppo rumore',
'priorita' => 'Media',
'stato' => 'Chiuso',
'data_apertura' => now()->subDays(5),
'data_chiusura_effettiva' => now(),
'created_at' => now()->subDays(2),
'updated_at' => now(),
]
];
foreach ($tickets as $ticket) {
Ticket::create($ticket);
}
$this->info('🎫 Creati 4 tickets di test');
}
private function createTestRate()
{
// TODO: Implementare rate con la nuova struttura
$this->info('⏭️ Creazione rate temporaneamente disabilitata');
return;
// Prendi il primo stabile disponibile
$stabile = Stabile::first();
if (!$stabile) {
$this->error('❌ Nessuno stabile trovato. Crea prima uno stabile.');
return;
}
$rate = [
[
'stabile_id' => $stabile->id,
'importo' => 250.00,
'data_scadenza' => Carbon::now()->subDays(10), // Scaduta
'stato' => 'non_pagata',
'descrizione' => 'Rata mensile gennaio 2025',
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'importo' => 250.00,
'data_scadenza' => Carbon::now()->subDays(5), // Scaduta
'stato' => 'non_pagata',
'descrizione' => 'Rata mensile febbraio 2025',
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'importo' => 250.00,
'data_scadenza' => Carbon::now()->addDays(20),
'stato' => 'non_pagata',
'descrizione' => 'Rata mensile marzo 2025',
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'importo' => 250.00,
'data_scadenza' => Carbon::now()->subDays(30),
'data_pagamento' => Carbon::now()->subDays(25),
'stato' => 'pagata',
'descrizione' => 'Rata mensile dicembre 2024',
'created_at' => now(),
'updated_at' => now(),
]
];
foreach ($rate as $rata) {
Rata::create($rata);
}
$this->info('💰 Create 4 rate di test');
}
private function createTestAssemblee()
{
// TODO: Implementare assemblee quando la tabella sarà disponibile
$this->info('⏭️ Creazione assemblee temporaneamente disabilitata');
return;
// Prendi il primo stabile disponibile
$stabile = Stabile::first();
if (!$stabile) {
$this->error('❌ Nessuno stabile trovato. Crea prima uno stabile.');
return;
}
$assemblee = [
[
'stabile_id' => $stabile->id,
'titolo' => 'Assemblea Ordinaria Gennaio 2025',
'data_assemblea' => Carbon::now()->addDays(15),
'luogo' => 'Sala condominiale',
'stato' => 'programmata',
'created_at' => now(),
'updated_at' => now(),
],
[
'stabile_id' => $stabile->id,
'titolo' => 'Assemblea Straordinaria - Rifacimento tetto',
'data_assemblea' => Carbon::now()->addDays(30),
'luogo' => 'Sala condominiale',
'stato' => 'bozza',
'created_at' => now(),
'updated_at' => now(),
]
];
foreach ($assemblee as $assemblea) {
Assemblea::create($assemblea);
}
$this->info('🏛️ Create 2 assemblee di test');
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Allegato;
use App\Models\Stabile;
use App\Models\User;
use App\Models\MovimentoContabile;
class AllegatiSeeder extends Seeder
{
/**
* Seeder per la tabella allegati con dati di test realistici.
* Segue le best practice Laravel con chiavi primarie 'id' standard.
*/
public function run(): void
{
// Recupera alcuni stabili e utenti esistenti per le relazioni
$stabili = Stabile::limit(3)->get();
$users = User::limit(2)->get();
$movimenti = MovimentoContabile::limit(5)->get();
$this->command->info("Trovati: {$stabili->count()} stabili, {$users->count()} utenti, {$movimenti->count()} movimenti");
if ($stabili->isEmpty() || $users->isEmpty()) {
$this->command->warn('Assicurati che esistano stabili e utenti prima di eseguire questo seeder.');
$this->command->info('Creazione dati di esempio senza relazioni...');
}
$allegatiData = [
[
'stabile_id' => $stabili->first()?->id,
'nome_file_originale' => 'Fattura_Energia_Gennaio_2025.pdf',
'nome_file_storage' => 'fattura_energia_jan_2025_' . uniqid() . '.pdf',
'percorso_file_storage' => 'allegati/fatture/2025/01/',
'tipo_mime' => 'application/pdf',
'dimensione_byte' => 245760, // ~240KB
'descrizione' => 'Fattura energia elettrica gennaio 2025',
'allegabile_type' => MovimentoContabile::class,
'allegabile_id' => $movimenti->first()?->id,
'user_id' => $users->first()?->id,
'tags' => json_encode(['fattura', 'energia', 'utilities']),
],
[
'stabile_id' => $stabili->first()?->id,
'nome_file_originale' => 'Preventivo_Manutenzione_Ascensore.docx',
'nome_file_storage' => 'preventivo_ascensore_' . uniqid() . '.docx',
'percorso_file_storage' => 'allegati/preventivi/2025/',
'tipo_mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dimensione_byte' => 89120, // ~87KB
'descrizione' => 'Preventivo per manutenzione straordinaria ascensore',
'allegabile_type' => MovimentoContabile::class,
'allegabile_id' => $movimenti->skip(1)->first()?->id,
'user_id' => $users->first()?->id,
'tags' => json_encode(['preventivo', 'ascensore', 'manutenzione']),
],
[
'stabile_id' => $stabili->get(1)?->id ?? $stabili->first()?->id,
'nome_file_originale' => 'Foto_Danni_Facciata.jpg',
'nome_file_storage' => 'foto_danni_facciata_' . uniqid() . '.jpg',
'percorso_file_storage' => 'allegati/foto/danni/2025/',
'tipo_mime' => 'image/jpeg',
'dimensione_byte' => 2097152, // ~2MB
'descrizione' => 'Fotografia dei danni alla facciata ovest',
'allegabile_type' => MovimentoContabile::class,
'allegabile_id' => $movimenti->skip(2)->first()?->id,
'user_id' => $users->last()?->id,
'tags' => json_encode(['foto', 'danni', 'facciata']),
],
];
foreach ($allegatiData as $data) {
// Crea l'allegato sempre, anche se alcune relazioni sono null
$allegato = Allegato::create($data);
$this->command->info("Creato allegato: {$data['nome_file_originale']} (ID: {$allegato->id}, Codice: {$allegato->codice_allegato})");
}
$this->command->info('Seeder AllegatiSeeder completato con successo!');
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Amministratore;
use App\Models\User;
use App\Models\Stabile;
class AmministratoriSeeder extends Seeder
{
/**
* Seeder per amministratori con codici alfanumerici e sistema multi-database.
*/
public function run(): void
{
$this->command->info('Creazione amministratori di test...');
// Amministratore 1 - Con database dedicato
$admin1 = Amministratore::create([
'ragione_sociale' => 'Studio Amministrativo Rossi & Associati',
'nome' => 'Mario',
'cognome' => 'Rossi',
'codice_fiscale' => 'RSSMRA70A01H501X',
'partita_iva' => '12345678901',
'email' => 'mario.rossi@studiorossi.it',
'telefono' => '06-12345678',
'cellulare' => '335-1234567',
'indirizzo' => 'Via Roma 123',
'cap' => '00100',
'citta' => 'Roma',
'provincia' => 'RM',
'database_attivo' => true,
'commissione_percentuale' => 2.50,
'costo_fisso_mensile' => 150.00,
'stato' => 'attivo',
'data_inizio_attivita' => '2024-01-01',
]);
// Amministratore 2 - Database condiviso
$admin2 = Amministratore::create([
'ragione_sociale' => 'Amministrazioni Bianchi SRL',
'nome' => 'Laura',
'cognome' => 'Bianchi',
'codice_fiscale' => 'BNCLRA80B02H501Y',
'partita_iva' => '09876543210',
'email' => 'laura.bianchi@ammbianchi.it',
'telefono' => '06-87654321',
'cellulare' => '339-9876543',
'indirizzo' => 'Via Milano 456',
'cap' => '00200',
'citta' => 'Roma',
'provincia' => 'RM',
'database_attivo' => false,
'commissione_percentuale' => 3.00,
'costo_fisso_mensile' => 100.00,
'stato' => 'attivo',
'data_inizio_attivita' => '2024-06-01',
]);
$this->command->info("Creati amministratori:");
$this->command->info("- {$admin1->ragione_sociale} (Codice: {$admin1->codice_amministratore})");
$this->command->info("- {$admin2->ragione_sociale} (Codice: {$admin2->codice_amministratore})");
// Crea stabili di test per i due amministratori
$this->createStabiliPerAmministratori($admin1, $admin2);
$this->command->info('Seeder AmministratoriSeeder completato con successo!');
}
private function createStabiliPerAmministratori($admin1, $admin2)
{
// Stabili per Amministratore 1
Stabile::create([
'amministratore_id' => $admin1->id,
'denominazione' => 'Condominio Villa Roma',
'indirizzo' => 'Via del Corso 100',
'cap' => '00186',
'citta' => 'Roma',
'provincia' => 'RM',
'codice_fiscale' => 'VLRM001234567890',
'stato' => 'attivo',
]);
Stabile::create([
'amministratore_id' => $admin1->id,
'denominazione' => 'Condominio Sole Nascente',
'indirizzo' => 'Via Nazionale 250',
'cap' => '00184',
'citta' => 'Roma',
'provincia' => 'RM',
'codice_fiscale' => 'SLNS001234567891',
'stato' => 'attivo',
]);
// Stabili per Amministratore 2
Stabile::create([
'amministratore_id' => $admin2->id,
'denominazione' => 'Condominio Verde Pineta',
'indirizzo' => 'Via Tiburtina 500',
'cap' => '00159',
'citta' => 'Roma',
'provincia' => 'RM',
'codice_fiscale' => 'VRDP001234567892',
'stato' => 'attivo',
]);
$this->command->info("Creati stabili di test per entrambi gli amministratori");
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Console\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Chiama solo il seeder che abbiamo creato.
$this->call([
// SuperAdminSeeder::class, // Questo seeder è ora inglobato in TestSetupSeeder
\App\Console\Seeders\TestSetupSeeder::class, // Chiama il seeder principale di setup
\App\Console\Seeders\ImpostazioniSeeder::class,
]);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ImpostazioniSeeder extends Seeder
{
public function run(): void
{
DB::table('impostazioni')->insertOrIgnore([
[
'chiave' => 'sidebar_bg',
'valore' => '#fde047',
'descrizione' => 'Colore di sfondo sidebar (giallo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text',
'valore' => '#1e293b',
'descrizione' => 'Colore testo sidebar',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent',
'valore' => '#6366f1',
'descrizione' => 'Colore accento sidebar (indigo)',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_bg_dark',
'valore' => '#23272e',
'descrizione' => 'Colore sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_text_dark',
'valore' => '#f1f5f9',
'descrizione' => 'Colore testo sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
[
'chiave' => 'sidebar_accent_dark',
'valore' => '#fbbf24',
'descrizione' => 'Colore accento sidebar dark mode',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Console\Seeders;
use App\Models\MovimentoContabile;
use App\Models\Stabile;
use App\Models\User;
use Illuminate\Database\Seeder;
use Carbon\Carbon;
class MovimentiContabiliSeeder extends Seeder
{
public function run()
{
// Recupera uno stabile e un utente per i test
$stabile = Stabile::first();
$user = User::first();
if (!$stabile || !$user) {
$this->command->error('Mancano Stabili o Utenti. Esegui prima gli altri seeder.');
return;
}
$this->command->info('Creazione movimenti contabili di test...');
// Movimenti in Prima Nota
$movimentiPrimaNota = [
[
'stabile_id' => $stabile->id, // Usa 'id' invece di 'id_stabile'
'data_registrazione' => now()->subDays(5),
'descrizione' => 'Fattura elettrica Enel - Gen 2025',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'ordinario',
'importo_totale' => 156.78,
'iva' => 33.92,
'importo_netto' => 122.86,
'stato_movimento' => 'prima_nota',
'numero_documento' => 'ENEL2025001',
'protocollo' => 'PROT001/2025',
'creato_da' => $user->id,
],
[
'stabile_id' => $stabile->id, // Usa 'id' invece di 'id_stabile'
'data_registrazione' => now()->subDays(3),
'descrizione' => 'Fattura gas Eni - Gen 2025',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'ordinario',
'importo_totale' => 89.45,
'iva' => 19.48,
'importo_netto' => 69.97,
'stato_movimento' => 'prima_nota',
'numero_documento' => 'ENI2025001',
'protocollo' => 'PROT002/2025',
'creato_da' => $user->id,
],
[
'stabile_id' => $stabile->id, // Usa 'id' invece di 'id_stabile'
'data_registrazione' => now()->subDays(2),
'descrizione' => 'Rata condominiale Gennaio 2025',
'tipo_movimento' => 'entrata',
'categoria_movimento' => 'ordinario',
'importo_totale' => 2340.00,
'iva' => 0.00,
'importo_netto' => 2340.00,
'stato_movimento' => 'prima_nota',
'protocollo' => 'PROT003/2025',
'creato_da' => $user->id,
]
];
// Movimenti già Confermati
$movimentiConfermati = [
[
'stabile_id' => $stabile->id, // Usa 'id' invece di 'id_stabile'
'data_registrazione' => now()->subDays(10),
'descrizione' => 'Pulizia scale - Dicembre 2024',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'ordinario',
'importo_totale' => 250.00,
'iva' => 55.00,
'importo_netto' => 195.00,
'stato_movimento' => 'confermato',
'data_conferma' => now()->subDays(8),
'confermato_da' => $user->id,
'numero_documento' => 'PULIZIE001',
'protocollo' => 'PROT020/2024',
'creato_da' => $user->id,
],
[
'stabile_id' => $stabile->id, // Usa 'id' invece di 'id_stabile'
'data_registrazione' => now()->subDays(15),
'descrizione' => 'Riparazione ascensore',
'tipo_movimento' => 'uscita',
'categoria_movimento' => 'straordinario',
'importo_totale' => 850.00,
'iva' => 187.00,
'importo_netto' => 663.00,
'stato_movimento' => 'confermato',
'data_conferma' => now()->subDays(12),
'confermato_da' => $user->id,
'numero_documento' => 'ASC2024001',
'protocollo' => 'PROT019/2024',
'creato_da' => $user->id,
]
];
// Crea movimenti in prima nota
foreach ($movimentiPrimaNota as $movimento) {
MovimentoContabile::create($movimento);
}
// Crea movimenti confermati
foreach ($movimentiConfermati as $movimento) {
MovimentoContabile::create($movimento);
}
$this->command->info('Creati ' . count($movimentiPrimaNota) . ' movimenti in Prima Nota');
$this->command->info('Creati ' . count($movimentiConfermati) . ' movimenti Confermati');
$this->command->info('Seeder MovimentiContabili completato!');
}
}

View File

@ -0,0 +1,379 @@
<?php
namespace App\Console\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Amministratore;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\Soggetto;
use App\Models\Proprieta;
use App\Models\TabellaMillesimale;
use App\Models\DettaglioTabellaMillesimale;
use App\Models\PianoContiCondominio;
use App\Models\Gestione;
use App\Models\Preventivo;
use App\Models\VocePreventivo;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Illuminate\Support\Facades\Hash;
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
class TestSetupSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pulisce la cache dei permessi
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 1. Crea i ruoli
// Usa Spatie\Permission\Models\Role per assegnare i ruoli
$superAdminRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'super-admin', 'guard_name' => 'web']);
// Ruoli in italiano per la gestione condominiale
$amministratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
$collaboratoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'collaboratore', 'guard_name' => 'web']);
$condominoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'condomino', 'guard_name' => 'web']);
$fornitoreRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'fornitore', 'guard_name' => 'web']);
$inquilinoRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'inquilino', 'guard_name' => 'web']);
$ospiteRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'ospite', 'guard_name' => 'web']);
$serviziRole = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'servizi', 'guard_name' => 'web']);
$this->command->info('Ruoli creati/verificati.');
// Ruoli di base per sviluppo (rimosso uso di App\Models\Role e campo label)
// Tutti i ruoli sono ora gestiti solo tramite Spatie\Permission\Models\Role
// 2. Crea l'utente Super Admin
// Rimosso il campo 'role' diretto, verrà assegnato tramite Spatie
$superAdmin = User::firstOrCreate(
['email' => 'superadmin@example.com'],
[
'name' => 'Super Admin',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'super-admin' verrà assegnato tramite Spatie
$this->command->info('Utente Super Admin creato/aggiornato: ' . $superAdmin->email); // Variabile corretta
// 2. Crea un Utente Amministratore
$adminUser = User::firstOrCreate(
['email' => 'admin@example.com'],
[
'name' => 'Amministratore Test',
'password' => Hash::make('password'), // Cambia questa password in produzione!
'email_verified_at' => now(),
]
);
// Il ruolo 'admin' verrà assegnato tramite Spatie
$this->command->info('Utente Amministratore creato/aggiornato: ' . $adminUser->email);
// 3. Crea un Record Amministratore (collegato all'utente admin)
$amministratore = Amministratore::firstOrCreate(
['user_id' => $adminUser->id],
[
'nome' => 'Mario',
'cognome' => 'Rossi',
'denominazione_studio' => 'Studio Rossi Amministrazioni',
'partita_iva' => '12345678901',
'codice_fiscale_studio' => 'RSSMRA80A01H501K',
'indirizzo_studio' => 'Via Roma 10',
'cap_studio' => '00100',
'citta_studio' => 'Roma',
'provincia_studio' => 'RM',
'telefono_studio' => '061234567',
'email_studio' => 'studio.rossi@example.com',
'pec_studio' => 'studio.rossi@pec.it',
]
);
$this->command->info('Record Amministratore creato/aggiornato: ' . $amministratore->nome . ' ' . $amministratore->cognome);
// 4. Crea un Condominio di Test
$stabile = Stabile::firstOrCreate(
['denominazione' => 'Stabile Test Via Milano 1'],
[
'amministratore_id' => $amministratore->id_amministratore,
'indirizzo' => 'Via Milano 1',
'cap' => '20100',
'citta' => 'Milano',
'provincia' => 'MI',
'codice_fiscale' => 'CNDMLN00001A001A',
'note' => 'Condominio di test per lo sviluppo.',
'stato' => 'attivo',
]
);
$this->command->info('Stabile di Test creato/aggiornato: ' . $stabile->denominazione);
// 5. Crea Unità Immobiliari di Test
$unita1 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '1', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '1',
'categoria_catastale' => 'A/3',
'superficie' => 80.50,
'vani' => 4.5,
'indirizzo' => null,
'note' => 'Appartamento di test A1',
]
);
$unita2 = UnitaImmobiliare::firstOrCreate(
['stabile_id' => $stabile->id, 'interno' => '2', 'scala' => 'A', 'fabbricato' => 'Principale'],
[
'piano' => '1',
'subalterno' => '2',
'categoria_catastale' => 'A/3',
'superficie' => 70.00,
'vani' => 3.5,
'indirizzo' => null,
'note' => 'Appartamento di test A2',
]
);
$this->command->info('Unità Immobiliari di Test create.');
// 6. Crea Soggetti di Test
$soggettoProprietario1 = Soggetto::firstOrCreate(['email' => 'proprietario1@example.com'], ['nome' => 'Giuseppe', 'cognome' => 'Verdi', 'tipo' => 'proprietario', 'codice_fiscale' => 'VRDGPP80A01H501A']);
$soggettoProprietario2 = Soggetto::firstOrCreate(['email' => 'proprietario2@example.com'], ['nome' => 'Maria', 'cognome' => 'Bianchi', 'tipo' => 'proprietario', 'codice_fiscale' => 'BNCMRA85B02H502B']);
$soggettoInquilino = Soggetto::firstOrCreate(['email' => 'inquilino@example.com'], ['nome' => 'Luca', 'cognome' => 'Neri', 'tipo' => 'inquilino', 'codice_fiscale' => 'NRELCA90C03H503C']);
$this->command->info('Soggetti di Test creati.');
// 7. Collega Soggetti alle Unità (Proprieta)
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2020-01-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario1->id ?? $soggettoProprietario1->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'nudo_proprietario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoProprietario2->id ?? $soggettoProprietario2->id_soggetto,
'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita
], [
'tipo_diritto' => 'usufruttuario',
'percentuale_possesso' => 100.00,
'data_inizio' => '2022-03-01'
]);
Proprieta::firstOrCreate([
'soggetto_id' => $soggettoInquilino->id ?? $soggettoInquilino->id_soggetto,
'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita
], [
'tipo_diritto' => 'inquilino',
'percentuale_possesso' => 100.00,
'data_inizio' => '2023-06-15'
]);
$this->command->info('Relazioni Soggetto-Unità create.');
// 8. Crea una Tabella Millesimale di Test
$tabellaA = TabellaMillesimale::firstOrCreate(
['stabile_id' => $stabile->id, 'nome_tabella_millesimale' => 'Tabella A - Proprietà'],
['descrizione' => 'Ripartizione spese in base ai millesimi di proprietà generale.']
);
// Fix: recupera la chiave primaria corretta se non presente
if (!$tabellaA->id) {
// Prova a ricaricare dal DB se firstOrCreate restituisce un oggetto senza la chiave primaria
$tabellaA = TabellaMillesimale::where('stabile_id', $stabile->id)
->where('nome_tabella_millesimale', 'Tabella A - Proprietà')
->first();
}
if (!$tabellaA || !$tabellaA->id) {
$this->command->error('Errore: la tabella millesimale non è stata creata correttamente!');
return;
}
$this->command->info('Tabella Millesimale di Test creata.');
// 9. Crea Dettagli Millesimali per le unità
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita1->id ?? $unita1->id_unita],
['millesimi' => 500.0000]
);
DettaglioTabellaMillesimale::firstOrCreate(
['tabella_millesimale_id' => $tabellaA->id, 'unita_immobiliare_id' => $unita2->id ?? $unita2->id_unita],
['millesimi' => 500.0000]
);
$this->command->info('Dettagli Millesimali creati.');
// 10. Crea una Gestione di Test
$gestione2024 = Gestione::firstOrCreate(
['stabile_id' => $stabile->id, 'anno_gestione' => 2024, 'tipo_gestione' => 'Ord.'],
['data_inizio' => '2024-01-01', 'data_fine' => '2024-12-31', 'stato' => 'aperta']
);
$this->command->info('Gestione di Test creata.');
// Aggiungiamo anche la gestione 2025
$gestione2025 = Gestione::firstOrCreate(
['stabile_id' => $stabile->id, 'anno_gestione' => 2025, 'tipo_gestione' => 'Ord.'],
['data_inizio' => '2025-01-01', 'stato' => 'aperta']
);
$this->command->info('Gestione 2025 creata.');
// 11. Crea un Piano dei Conti per lo Stabile (esempio base)
$contoPulizie = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.PUL'],
['descrizione' => 'Spese di Pulizia Scale', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$contoAssicurazione = PianoContiCondominio::firstOrCreate(
['stabile_id' => $stabile->id, 'codice' => 'SP.ASS'],
['descrizione' => 'Assicurazione Fabbricato', 'tipo_conto' => 'ECONOMICO_COSTO']
);
$this->command->info('Piano dei Conti di Test creato.');
/*// 12. Crea un Preventivo di Test
$preventivo2024 = Preventivo::firstOrCreate(
['id_gestione' => $gestione2024->id_gestione],
['descrizione' => 'Preventivo Ordinario 2024', 'stato' => 'APPROVATO']
);
$this->command->info('Preventivo di Test creato.');
// 13. Crea Voci di Preventivo
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoPulizie->id_conto_condominio_pc], ['importo_previsto' => 1200.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
VocePreventivo::firstOrCreate(['id_preventivo' => $preventivo2024->id_preventivo, 'id_piano_conto_condominio_pc' => $contoAssicurazione->id_conto_condominio_pc], ['importo_previsto' => 800.00, 'id_tabella_millesimale_ripartizione' => $tabellaA->id_tabella_millesimale]);
$this->command->info('Voci di Preventivo create.'); */
// Creazione Permessi (Esempio)
$gestioneCondominiPermission = Permission::firstOrCreate(['name' => 'gestione-condomini']);
$visualizzaReportPermission = Permission::firstOrCreate(['name' => 'visualizza-report']);
Permission::firstOrCreate(['name' => 'view-stabili']);
Permission::firstOrCreate(['name' => 'manage-stabili']); // Permesso generico per le azioni CRUD
// Permessi per la gestione utenti (Super Admin)
Permission::firstOrCreate(['name' => 'create-users']);
Permission::firstOrCreate(['name' => 'view-users']);
Permission::firstOrCreate(['name' => 'manage-users']); // Include create, edit, delete, update role
Permission::firstOrCreate(['name' => 'impersonate-users']);
// Permessi per la gestione amministratori (Super Admin)
Permission::firstOrCreate(['name' => 'view-amministratori']);
Permission::firstOrCreate(['name' => 'manage-amministratori']); // Include create, edit, delete
// Permessi per la gestione categorie ticket (Super Admin)
Permission::firstOrCreate(['name' => 'view-categorie-ticket']);
Permission::firstOrCreate(['name' => 'manage-categorie-ticket']); // Include create, edit, delete
// Permessi per la gestione soggetti (Admin)
Permission::firstOrCreate(['name' => 'view-soggetti']);
Permission::firstOrCreate(['name' => 'manage-soggetti']); // Include create, edit, delete
// Permessi per la gestione fornitori (Admin)
Permission::firstOrCreate(['name' => 'view-fornitori']);
Permission::firstOrCreate(['name' => 'manage-fornitori']);
// Permessi per la gestione ticket (Admin)
Permission::firstOrCreate(['name' => 'view-tickets']);
Permission::firstOrCreate(['name' => 'manage-tickets']);
// Permessi per la gestione unità immobiliari (Admin)
Permission::firstOrCreate(['name' => 'view-unita-immobiliari']);
Permission::firstOrCreate(['name' => 'manage-unita-immobiliari']);
// Permessi per le impostazioni e API Tokens (Admin)
Permission::firstOrCreate(['name' => 'view-impostazioni']);
Permission::firstOrCreate(['name' => 'manage-api-tokens']);
Permission::firstOrCreate(['name' => 'view-rubrica']);
// Aggiungi qui altri permessi specifici per il tuo progetto
// Assegnazione Permessi ai Ruoli (Esempio)
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
$adminRole = \Spatie\Permission\Models\Role::where('name', 'admin')->first();
$superAdminRole = \Spatie\Permission\Models\Role::where('name', 'super-admin')->first();
$amministratoreRole = \Spatie\Permission\Models\Role::where('name', 'amministratore')->first();
if ($amministratoreRole) {
$amministratoreRole->givePermissionTo([
'visualizza-report',
'view-stabili', 'manage-stabili',
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: permessi non assegnati.");
}
// Assegna i permessi al ruolo 'admin'
$adminRole->givePermissionTo([
'view-soggetti', 'manage-soggetti',
'view-fornitori', 'manage-fornitori',
'view-tickets', 'manage-tickets',
'view-unita-immobiliari', 'manage-unita-immobiliari',
'view-impostazioni', 'manage-api-tokens', 'view-rubrica',
]);
// Assegna il ruolo 'amministratore' all'utente di test per permettergli di gestire gli stabili
if ($amministratoreRole) {
$adminUser->assignRole('amministratore');
} else {
$this->command->warn("Ruolo 'amministratore' non trovato: non assegnato all'utente di test.");
}
// Assegna tutti i permessi al Super Admin
$superAdminRole->givePermissionTo(Permission::all());
$superAdmin->assignRole('super-admin');
$this->command->info('Setup di test completato con successo!');
}
}
// Questo seeder crea un ambiente di test con un utente Super Admin, un Amministratore, un Condominio e alcune Unità Immobiliari con Soggetti associati.
// Assicurati di eseguire questo seeder con il comando `php artisan db:seed --class=TestSetupSeeder` per popolare il database con i dati di test.
// Puoi modificare le email e le password per adattarle alle tue esigenze di test.
// Assicurati di avere i modelli e le migrazioni corretti prima di eseguire questo seeder.
// Questo seeder è utile per testare le funzionalità del tuo gestionale senza dover inserire manualmente i dati ogni volta.
// Puoi anche estendere questo seeder per aggiungere ulteriori dati di test come spese, entrate, verbali, ecc.
// Assicurati di avere le relazioni corrette nei modelli Soggetto, UnitaImmobiliare e SoggettoUnita per gestire le associazioni tra soggetti e unità immobiliari.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Assicurati di eseguire il seeder in un ambiente di sviluppo o test, non in produzione, per evitare conflitti con i dati reali.
// Ricorda di aggiornare le password e le email in produzione per garantire la sicurezza del tuo gestionale.
// Questo seeder è progettato per essere eseguito una sola volta per impostare un ambiente di test iniziale.
// Puoi eseguire nuovamente il seeder per ripristinare lo stato di test, ma fai attenzione a non duplicare i dati esistenti.
// Se hai bisogno di modificare i dati di test, puoi farlo direttamente nel seeder o creare nuovi seeders per aggiungere ulteriori dati.
// Assicurati di avere le dipendenze corrette nel tuo progetto Laravel per eseguire questo seeder senza errori.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.
// Questo seeder è un ottimo punto di partenza per testare le funzionalità del tuo gestionale e garantire che tutto funzioni correttamente.
// Puoi anche utilizzare questo seeder come base per creare altri seeders specifici per le tue esigenze di test.

View File

@ -0,0 +1,252 @@
<?php
namespace App\Helpers;
use App\Models\Stabile;
use App\Models\Soggetto;
use App\Models\Ticket;
use App\Models\Fornitore;
use App\Models\Rata;
use App\Models\UnitaImmobiliare;
use App\Models\Assemblea;
use App\Models\MovimentoContabile;
use App\Models\Documento;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Carbon\Carbon;
class DashboardDataHelper
{
/**
* Ottiene dati reali per la dashboard principale
*/
public static function getDashboardData()
{
return Cache::remember('dashboard_data', 300, function () {
return [
'stabili' => self::getStabiliData(),
'condomini' => self::getCondominiData(),
'contabilita' => self::getContabilitaData(),
'tickets' => self::getTicketsData(),
'assemblee' => self::getAssembleeData(),
'sistema' => self::getSistemaData(),
];
});
}
/**
* Dati degli stabili con dettagli aggiuntivi
*/
private static function getStabiliData()
{
try {
$totaleStabili = Stabile::count();
$stabiliAttivi = Stabile::where('stato', 'attivo')->count();
$unitaTotali = UnitaImmobiliare::count();
$unitaOccupate = UnitaImmobiliare::whereHas('proprietari')->count();
return [
'totale' => $totaleStabili,
'attivi' => $stabiliAttivi,
'inattivi' => $totaleStabili - $stabiliAttivi,
'unita_totali' => $unitaTotali,
'unita_occupate' => $unitaOccupate,
'unita_libere' => $unitaTotali - $unitaOccupate,
'percentuale_occupazione' => $unitaTotali > 0 ? round(($unitaOccupate / $unitaTotali) * 100, 1) : 0,
];
} catch (\Exception $e) {
return [
'totale' => 0, 'attivi' => 0, 'inattivi' => 0,
'unita_totali' => 0, 'unita_occupate' => 0, 'unita_libere' => 0,
'percentuale_occupazione' => 0
];
}
}
/**
* Dati dei condomini con classificazioni
*/
private static function getCondominiData()
{
try {
$totaleCondomini = Soggetto::count();
$proprietari = Soggetto::where('tipo', 'proprietario')->count();
$inquilini = Soggetto::where('tipo', 'inquilino')->count();
return [
'totale' => $totaleCondomini,
'proprietari' => $proprietari,
'inquilini' => $inquilini,
'altri' => $totaleCondomini - $proprietari - $inquilini,
'percentuale_proprietari' => $totaleCondomini > 0 ? round(($proprietari / $totaleCondomini) * 100, 1) : 0,
];
} catch (\Exception $e) {
return [
'totale' => 0, 'proprietari' => 0, 'inquilini' => 0, 'altri' => 0,
'percentuale_proprietari' => 0
];
}
}
/**
* Dati della contabilità con trend
*/
private static function getContabilitaData()
{
try {
$oggi = Carbon::now();
$meseScorso = $oggi->copy()->subMonth();
$rateScadute = Rata::where('data_scadenza', '<', $oggi)
->where('stato', '!=', 'pagata')
->count();
$incassiMeseCorrente = Rata::whereMonth('data_pagamento', $oggi->month)
->whereYear('data_pagamento', $oggi->year)
->where('stato', 'pagata')
->sum('importo');
$incassiMeseScorso = Rata::whereMonth('data_pagamento', $meseScorso->month)
->whereYear('data_pagamento', $meseScorso->year)
->where('stato', 'pagata')
->sum('importo');
return [
'rate_scadute' => $rateScadute,
'rate_del_mese' => Rata::whereMonth('data_scadenza', $oggi->month)
->whereYear('data_scadenza', $oggi->year)
->count(),
'incassi_mese_corrente' => $incassiMeseCorrente,
'incassi_mese_scorso' => $incassiMeseScorso,
'trend_incassi' => $incassiMeseScorso > 0 ?
round((($incassiMeseCorrente - $incassiMeseScorso) / $incassiMeseScorso) * 100, 1) : 0,
'movimenti_mese' => MovimentoContabile::whereMonth('data_movimento', $oggi->month)
->whereYear('data_movimento', $oggi->year)
->count(),
];
} catch (\Exception $e) {
return [
'rate_scadute' => 0, 'rate_del_mese' => 0,
'incassi_mese_corrente' => 0, 'incassi_mese_scorso' => 0,
'trend_incassi' => 0, 'movimenti_mese' => 0
];
}
}
/**
* Dati dei tickets con priorità e tempi
*/
private static function getTicketsData()
{
try {
$ticketsAperti = Ticket::where('stato', 'aperto')->count();
$ticketsUrgenti = Ticket::where('priorita', 'alta')
->where('stato', '!=', 'chiuso')
->count();
$ticketsInLavorazione = Ticket::where('stato', 'in_lavorazione')->count();
$ticketsChiusiOggi = Ticket::where('stato', 'chiuso')
->whereDate('updated_at', Carbon::today())
->count();
return [
'aperti' => $ticketsAperti,
'urgenti' => $ticketsUrgenti,
'in_lavorazione' => $ticketsInLavorazione,
'chiusi_oggi' => $ticketsChiusiOggi,
'totali' => Ticket::count(),
'percentuale_risoluzione' => $ticketsAperti + $ticketsInLavorazione > 0 ?
round(($ticketsChiusiOggi / ($ticketsAperti + $ticketsInLavorazione + $ticketsChiusiOggi)) * 100, 1) : 100,
];
} catch (\Exception $e) {
return [
'aperti' => 0, 'urgenti' => 0, 'in_lavorazione' => 0,
'chiusi_oggi' => 0, 'totali' => 0, 'percentuale_risoluzione' => 100
];
}
}
/**
* Dati delle assemblee con calendario
*/
private static function getAssembleeData()
{
try {
$oggi = Carbon::now();
$prossimi30Giorni = $oggi->copy()->addDays(30);
return [
'prossime' => Assemblea::where('data_assemblea', '>', $oggi)->count(),
'prossimi_30_giorni' => Assemblea::whereBetween('data_assemblea', [$oggi, $prossimi30Giorni])->count(),
'questo_mese' => Assemblea::whereMonth('data_assemblea', $oggi->month)
->whereYear('data_assemblea', $oggi->year)
->count(),
'delibere_da_approvare' => Assemblea::where('stato', 'bozza')->count(),
'verbali_da_completare' => Assemblea::where('stato', 'verbale_incompleto')->count(),
];
} catch (\Exception $e) {
return [
'prossime' => 0, 'prossimi_30_giorni' => 0, 'questo_mese' => 0,
'delibere_da_approvare' => 0, 'verbali_da_completare' => 0
];
}
}
/**
* Dati generali del sistema
*/
private static function getSistemaData()
{
try {
return [
'utenti_attivi' => DB::table('users')->where('active', true)->count(),
'ultimo_backup' => self::getLastBackupDate(),
'spazio_documenti' => self::getDocumentsSpaceUsage(),
'uptime' => self::getSystemUptime(),
'versione' => config('app.version', '2.1.0'),
];
} catch (\Exception $e) {
return [
'utenti_attivi' => 1,
'ultimo_backup' => 'Non disponibile',
'spazio_documenti' => 'Non disponibile',
'uptime' => 'Non disponibile',
'versione' => '2.1.0'
];
}
}
/**
* Ottiene la data dell'ultimo backup
*/
private static function getLastBackupDate()
{
// Implementazione placeholder - sostituire con logica reale
return Carbon::now()->subDays(1)->format('d/m/Y H:i');
}
/**
* Ottiene l'utilizzo dello spazio per i documenti
*/
private static function getDocumentsSpaceUsage()
{
// Implementazione placeholder - sostituire con logica reale
return '245 MB utilizzati';
}
/**
* Ottiene l'uptime del sistema
*/
private static function getSystemUptime()
{
// Implementazione placeholder - sostituire con logica reale
return '15 giorni, 8 ore';
}
/**
* Pulisce la cache
*/
public static function clearCache()
{
Cache::forget('dashboard_data');
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Auth;
class MenuHelper
{
/**
* Verifica se l'utente può accedere a una specifica sezione del menu
*/
public static function canUserAccessMenu($menuSection, $userRole = null)
{
// Se non specificato, prende il ruolo dall'utente autenticato
$userRole = $userRole ?? self::getCurrentUserRole();
// Definizione permessi per ogni ruolo
$permissions = [
'super_admin' => ['*'], // Accesso completo
'admin' => [
'dashboard', 'stabili', 'condomini', 'contabilita', 'fiscale',
'assemblee', 'risorse-economiche', 'comunicazioni', 'affitti',
'pratiche', 'consumi', 'tickets', 'impostazioni', 'utenti'
],
'amministratore' => [
'dashboard', 'stabili', 'condomini', 'contabilita', 'fiscale',
'assemblee', 'risorse-economiche', 'comunicazioni', 'affitti',
'pratiche', 'consumi', 'tickets'
],
'collaboratore' => [
'dashboard', 'stabili', 'condomini', 'contabilita',
'comunicazioni', 'tickets', 'pratiche'
],
'ragioniere' => [
'dashboard', 'contabilita', 'fiscale', 'risorse-economiche',
'comunicazioni', 'tickets'
],
'condomino' => [
'dashboard', 'comunicazioni', 'tickets'
],
'guest' => []
];
// Super admin ha accesso a tutto
if ($userRole === 'super_admin') {
return true;
}
// Verifica permessi specifici
$userPermissions = $permissions[$userRole] ?? [];
return in_array($menuSection, $userPermissions);
}
/**
* Wrapper per controllare multiple sezioni
*/
public static function canUserAccessAnyMenu($menuSections, $userRole = null)
{
if (!is_array($menuSections)) {
$menuSections = [$menuSections];
}
foreach ($menuSections as $section) {
if (self::canUserAccessMenu($section, $userRole)) {
return true;
}
}
return false;
}
/**
* Ottiene il ruolo utente corrente
*/
public static function getCurrentUserRole()
{
if (Auth::check()) {
return Auth::user()->role ?? 'amministratore'; // Default per test
}
return 'guest';
}
/**
* Verifica se l'utente ha un ruolo specifico o superiore
*/
public static function hasMinimumRole($requiredRole, $userRole = null)
{
$userRole = $userRole ?? self::getCurrentUserRole();
$roleHierarchy = [
'guest' => 0,
'condomino' => 1,
'collaboratore' => 2,
'ragioniere' => 2,
'amministratore' => 3,
'admin' => 4,
'super_admin' => 5
];
$userLevel = $roleHierarchy[$userRole] ?? 0;
$requiredLevel = $roleHierarchy[$requiredRole] ?? 999;
return $userLevel >= $requiredLevel;
}
}

View File

@ -0,0 +1,199 @@
<?php
namespace App\Helpers;
use App\Models\Stabile;
use App\Models\Soggetto;
use App\Models\Ticket;
use App\Models\Fornitore;
use App\Models\Rata;
use App\Models\UnitaImmobiliare;
use App\Models\Assemblea;
use App\Models\MovimentoContabile;
use App\Models\Documento;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Carbon\Carbon;
class SidebarStatsHelper
{
/**
* Ottiene statistiche per la sidebar con cache
*/
public static function getStats()
{
return Cache::remember('sidebar_stats', 300, function () { // Cache per 5 minuti
return [
'stabili' => self::getStabiliStats(),
'condomini' => self::getCondominiStats(),
'tickets' => self::getTicketsStats(),
'contabilita' => self::getContabilitaStats(),
'fornitori' => self::getFornitoriStats(),
'assemblee' => self::getAssembleeStats(),
'documenti' => self::getDocumentiStats(),
];
});
}
/**
* Statistiche Stabili
*/
private static function getStabiliStats()
{
try {
$totaleUnita = UnitaImmobiliare::count();
$unitaOccupate = UnitaImmobiliare::whereHas('proprietari')->count();
return [
'totale' => Stabile::count(),
'attivi' => Stabile::where('stato', 'attivo')->count(),
'unita_totali' => $totaleUnita,
'unita_libere' => $totaleUnita - $unitaOccupate,
];
} catch (\Exception $e) {
return ['totale' => 0, 'attivi' => 0, 'unita_totali' => 0, 'unita_libere' => 0];
}
}
/**
* Statistiche Condomini
*/
private static function getCondominiStats()
{
try {
return [
'totale' => Soggetto::count(),
'proprietari' => Soggetto::where('tipo', 'proprietario')->count(),
'inquilini' => Soggetto::where('tipo', 'inquilino')->count(),
];
} catch (\Exception $e) {
return ['totale' => 0, 'proprietari' => 0, 'inquilini' => 0];
}
}
/**
* Statistiche Tickets
*/
private static function getTicketsStats()
{
try {
return [
'aperti' => Ticket::where('stato', 'aperto')->count(),
'urgenti' => Ticket::where('priorita', 'alta')
->where('stato', '!=', 'chiuso')
->count(),
'in_lavorazione' => Ticket::where('stato', 'in_lavorazione')->count(),
];
} catch (\Exception $e) {
return ['aperti' => 0, 'urgenti' => 0, 'in_lavorazione' => 0];
}
}
/**
* Statistiche Contabilità
*/
private static function getContabilitaStats()
{
try {
$oggi = Carbon::now();
$meseCorrente = $oggi->format('Y-m');
return [
'rate_scadute' => Rata::where('data_scadenza', '<', $oggi)
->where('stato', '!=', 'pagata')
->count(),
'incassi_mese' => Rata::whereMonth('data_pagamento', $oggi->month)
->whereYear('data_pagamento', $oggi->year)
->where('stato', 'pagata')
->sum('importo'),
'movimenti_mese' => MovimentoContabile::whereMonth('data_movimento', $oggi->month)
->whereYear('data_movimento', $oggi->year)
->count(),
];
} catch (\Exception $e) {
return ['rate_scadute' => 0, 'incassi_mese' => 0, 'movimenti_mese' => 0];
}
}
/**
* Statistiche Fornitori
*/
private static function getFornitoriStats()
{
try {
return [
'totale' => Fornitore::count(),
'attivi' => Fornitore::where('stato', 'attivo')->count(),
'fatture_pending' => 0, // Da implementare quando avremo il modello Fattura
];
} catch (\Exception $e) {
return ['totale' => 0, 'attivi' => 0, 'fatture_pending' => 0];
}
}
/**
* Statistiche Assemblee
*/
private static function getAssembleeStats()
{
try {
$oggi = Carbon::now();
return [
'prossime' => Assemblea::where('data_assemblea', '>', $oggi)->count(),
'questo_mese' => Assemblea::whereMonth('data_assemblea', $oggi->month)
->whereYear('data_assemblea', $oggi->year)
->count(),
'delibere_da_approvare' => Assemblea::where('stato', 'bozza')->count(),
];
} catch (\Exception $e) {
return ['prossime' => 0, 'questo_mese' => 0, 'delibere_da_approvare' => 0];
}
}
/**
* Statistiche Documenti
*/
private static function getDocumentiStats()
{
try {
$oggi = Carbon::now();
return [
'totali' => Documento::count(),
'caricati_oggi' => Documento::whereDate('created_at', $oggi->toDateString())->count(),
'da_revisionare' => Documento::where('stato', 'bozza')->count(),
];
} catch (\Exception $e) {
return ['totali' => 0, 'caricati_oggi' => 0, 'da_revisionare' => 0];
}
}
/**
* Pulisce la cache delle statistiche
*/
public static function clearCache()
{
Cache::forget('sidebar_stats');
}
/**
* Badge per contatori con colori dinamici
*/
public static function getBadge($count, $type = 'info')
{
if ($count == 0) return '';
$colors = [
'success' => 'bg-success',
'warning' => 'bg-warning text-dark',
'danger' => 'bg-danger',
'info' => 'bg-info',
'primary' => 'bg-primary'
];
$colorClass = $colors[$type] ?? 'bg-secondary';
return "<span class=\"badge {$colorClass} ms-2\">{$count}</span>";
}
}

View File

@ -0,0 +1,271 @@
<?php
namespace App\Helpers;
use App\Models\UserSetting;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class ThemeHelper
{
/**
* Colori di default del tema NetGesCon
*/
const DEFAULT_THEME = [
'primary_color' => '#f39c12', // Giallo NetGesCon
'secondary_color' => '#3498db', // Blu
'success_color' => '#27ae60', // Verde
'danger_color' => '#e74c3c', // Rosso
'warning_color' => '#f39c12', // Arancione/Giallo
'info_color' => '#17a2b8', // Azzurro
'light_color' => '#f8f9fa', // Grigio chiaro
'dark_color' => '#343a40', // Grigio scuro
'sidebar_bg' => '#f39c12', // Sfondo sidebar (giallo)
'sidebar_text' => '#ffffff', // Testo sidebar (bianco)
'header_bg' => '#2c5aa0', // Sfondo header (blu)
'header_text' => '#ffffff', // Testo header (bianco)
'theme_mode' => 'light' // Modalità tema (light/dark)
];
/**
* Temi predefiniti disponibili
*/
const PRESET_THEMES = [
'netgescon_classic' => [
'name' => 'NetGesCon Classico',
'description' => 'Schema colori tradizionale NetGesCon',
'colors' => self::DEFAULT_THEME
],
'netgescon_blue' => [
'name' => 'NetGesCon Blu',
'description' => 'Variante blu professionale',
'colors' => [
'primary_color' => '#2c5aa0',
'secondary_color' => '#f39c12',
'success_color' => '#27ae60',
'danger_color' => '#e74c3c',
'warning_color' => '#f39c12',
'info_color' => '#17a2b8',
'light_color' => '#f8f9fa',
'dark_color' => '#343a40',
'sidebar_bg' => '#2c5aa0',
'sidebar_text' => '#ffffff',
'header_bg' => '#f39c12',
'header_text' => '#ffffff',
'theme_mode' => 'light'
]
],
'netgescon_green' => [
'name' => 'NetGesCon Verde',
'description' => 'Variante verde natura',
'colors' => [
'primary_color' => '#27ae60',
'secondary_color' => '#2c5aa0',
'success_color' => '#2ecc71',
'danger_color' => '#e74c3c',
'warning_color' => '#f39c12',
'info_color' => '#17a2b8',
'light_color' => '#f8f9fa',
'dark_color' => '#343a40',
'sidebar_bg' => '#27ae60',
'sidebar_text' => '#ffffff',
'header_bg' => '#2c5aa0',
'header_text' => '#ffffff',
'theme_mode' => 'light'
]
],
'netgescon_dark' => [
'name' => 'NetGesCon Dark',
'description' => 'Tema scuro per la sera',
'colors' => [
'primary_color' => '#f39c12',
'secondary_color' => '#6c757d',
'success_color' => '#28a745',
'danger_color' => '#dc3545',
'warning_color' => '#ffc107',
'info_color' => '#17a2b8',
'light_color' => '#343a40',
'dark_color' => '#212529',
'sidebar_bg' => '#212529',
'sidebar_text' => '#f39c12',
'header_bg' => '#343a40',
'header_text' => '#f39c12',
'theme_mode' => 'dark'
]
]
];
/**
* Ottiene i colori del tema per l'utente corrente
*/
public static function getUserTheme($userId = null): array
{
$userId = $userId ?? Auth::id();
if (!$userId) {
return self::DEFAULT_THEME;
}
$settings = UserSetting::where('user_id', $userId)
->whereIn('key', array_keys(self::DEFAULT_THEME))
->pluck('value', 'key')
->toArray();
// Merge con i valori di default
return array_merge(self::DEFAULT_THEME, $settings);
}
/**
* Salva le impostazioni del tema per un utente
*/
public static function saveUserTheme($userId, array $themeData): bool
{
try {
foreach ($themeData as $key => $value) {
if (array_key_exists($key, self::DEFAULT_THEME)) {
UserSetting::updateOrCreate(
['user_id' => $userId, 'key' => $key],
['value' => $value]
);
}
}
return true;
} catch (\Exception $e) {
Log::error('Errore salvataggio tema utente: ' . $e->getMessage());
return false;
}
}
/**
* Applica un tema predefinito a un utente
*/
public static function applyPresetTheme($userId, string $presetName): bool
{
if (!isset(self::PRESET_THEMES[$presetName])) {
return false;
}
return self::saveUserTheme($userId, self::PRESET_THEMES[$presetName]['colors']);
}
/**
* Genera CSS personalizzato per il tema utente
*/
public static function generateCustomCSS($userId = null): string
{
$theme = self::getUserTheme($userId);
return "
:root {
--netgescon-primary: {$theme['primary_color']};
--netgescon-secondary: {$theme['secondary_color']};
--netgescon-success: {$theme['success_color']};
--netgescon-danger: {$theme['danger_color']};
--netgescon-warning: {$theme['warning_color']};
--netgescon-info: {$theme['info_color']};
--netgescon-light: {$theme['light_color']};
--netgescon-dark: {$theme['dark_color']};
--netgescon-sidebar-bg: {$theme['sidebar_bg']};
--netgescon-sidebar-text: {$theme['sidebar_text']};
--netgescon-header-bg: {$theme['header_bg']};
--netgescon-header-text: {$theme['header_text']};
}
/* Sidebar personalizzata */
.netgescon-sidebar {
background-color: var(--netgescon-sidebar-bg) !important;
color: var(--netgescon-sidebar-text) !important;
}
.netgescon-sidebar .nav-link {
color: var(--netgescon-sidebar-text) !important;
}
.netgescon-sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1) !important;
}
.netgescon-sidebar .nav-link.active {
background-color: rgba(255, 255, 255, 0.2) !important;
}
/* Header personalizzato */
.netgescon-header {
background-color: var(--netgescon-header-bg) !important;
color: var(--netgescon-header-text) !important;
}
/* Pulsanti principali */
.btn-primary {
background-color: var(--netgescon-primary) !important;
border-color: var(--netgescon-primary) !important;
}
.btn-secondary {
background-color: var(--netgescon-secondary) !important;
border-color: var(--netgescon-secondary) !important;
}
/* Badge e alert */
.badge-primary {
background-color: var(--netgescon-primary) !important;
}
.alert-primary {
background-color: var(--netgescon-primary) !important;
border-color: var(--netgescon-primary) !important;
}
/* Tema scuro */
" . ($theme['theme_mode'] === 'dark' ? "
body {
background-color: var(--netgescon-dark) !important;
color: var(--netgescon-light) !important;
}
.card {
background-color: var(--netgescon-light) !important;
border-color: #6c757d !important;
}
.table-dark {
--bs-table-bg: var(--netgescon-dark);
}
" : "") . "
";
}
/**
* Ottiene tutti i temi predefiniti
*/
public static function getPresetThemes(): array
{
return self::PRESET_THEMES;
}
/**
* Valida un colore esadecimale
*/
public static function isValidHexColor(string $color): bool
{
return preg_match('/^#([a-f0-9]{3}){1,2}$/i', $color);
}
/**
* Converte un colore esadecimale in RGB
*/
public static function hexToRgb(string $hex): array
{
$hex = str_replace('#', '', $hex);
if (strlen($hex) == 3) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
return [
'r' => hexdec(substr($hex, 0, 2)),
'g' => hexdec(substr($hex, 2, 2)),
'b' => hexdec(substr($hex, 4, 2))
];
}
}

View File

@ -0,0 +1,12 @@
<?php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
if (!function_exists('impostazione')) {
function impostazione($chiave, $default = null) {
return Cache::rememberForever('impostazione_' . $chiave, function() use ($chiave, $default) {
$val = DB::table('impostazioni')->where('chiave', $chiave)->value('valore');
return $val ?? $default;
});
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AllegatoController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('admin.allegati.index', [
'title' => 'Allegati',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Allegati' => ''
]
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.allegati.create', [
'title' => 'Nuovo Allegato',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Allegati' => route('admin.allegati.index'),
'Nuovo Allegato' => ''
]
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// TODO: Implement store logic
return redirect()->route('admin.allegati.index')
->with('success', 'Allegato creato con successo.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
return view('admin.allegati.show', [
'title' => 'Dettaglio Allegato',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Allegati' => route('admin.allegati.index'),
'Dettaglio' => ''
]
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
return view('admin.allegati.edit', [
'title' => 'Modifica Allegato',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Allegati' => route('admin.allegati.index'),
'Modifica' => ''
]
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// TODO: Implement update logic
return redirect()->route('admin.allegati.index')
->with('success', 'Allegato aggiornato con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
// TODO: Implement destroy logic
return redirect()->route('admin.allegati.index')
->with('success', 'Allegato eliminato con successo.');
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AnagraficaCondominusController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('admin.anagrafica-condominiale.index', [
'title' => 'Anagrafica Condominiale',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Anagrafica Condominiale' => ''
]
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.anagrafica-condominiale.create', [
'title' => 'Nuova Anagrafica',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Anagrafica Condominiale' => route('admin.anagrafica-condominiale.index'),
'Nuova Anagrafica' => ''
]
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// TODO: Implement store logic
return redirect()->route('admin.anagrafica-condominiale.index')
->with('success', 'Anagrafica creata con successo.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
return view('admin.anagrafica-condominiale.show', [
'title' => 'Dettaglio Anagrafica',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Anagrafica Condominiale' => route('admin.anagrafica-condominiale.index'),
'Dettaglio' => ''
]
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
return view('admin.anagrafica-condominiale.edit', [
'title' => 'Modifica Anagrafica',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Anagrafica Condominiale' => route('admin.anagrafica-condominiale.index'),
'Modifica' => ''
]
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// TODO: Implement update logic
return redirect()->route('admin.anagrafica-condominiale.index')
->with('success', 'Anagrafica aggiornata con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
// TODO: Implement destroy logic
return redirect()->route('admin.anagrafica-condominiale.index')
->with('success', 'Anagrafica eliminata con successo.');
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ApiTokenController extends Controller
{
public function __construct()
{
$this->middleware('permission:manage-api-tokens');
}
public function index(Request $request)
{
$user = Auth::user();
// Laravel Sanctum non fornisce un modo diretto per listare i token senza Jetstream/Fortify UI.
// Solitamente si mostra un form per creare un nuovo token e si visualizza il token *solo una volta* dopo la creazione.
// L'utente deve copiarlo e salvarlo.
// Si possono elencare i token esistenti (senza mostrare il valore plain-text) per permetterne la revoca.
$tokens = $user->tokens; // Collection di PersonalAccessToken
return view('admin.api-tokens.index', ['tokens' => $tokens]);
}
public function store(Request $request)
{
$request->validate([
'token_name' => 'required|string|max:255',
]);
$user = Auth::user();
$tokenName = $request->input('token_name');
// Puoi definire delle 'abilities' (permessi) per il token se necessario
// $newToken = $user->createToken($tokenName, ['import:data']);
$newToken = $user->createToken($tokenName);
// IMPORTANTE: Il plainTextToken è visibile solo qui, subito dopo la creazione.
// Dovrai passarlo alla vista e informare l'utente di copiarlo immediatamente.
return back()->with('status', 'Token API creato con successo! Copia il token: ' . $newToken->plainTextToken);
}
public function destroy(Request $request, $tokenId)
{
$user = Auth::user();
$user->tokens()->where('id', $tokenId)->delete();
return back()->with('status', 'Token API revocato con successo.');
}
}

View File

@ -0,0 +1,528 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Assemblea;
use App\Models\OrdineGiorno;
use App\Models\Convocazione;
use App\Models\PresenzaAssemblea;
use App\Models\Votazione;
use App\Models\Verbale;
use App\Models\RegistroProtocollo;
use App\Models\Stabile;
use App\Models\Preventivo;
use App\Models\TabellaMillesimale;
use App\Models\Soggetto;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
class AssembleaController extends Controller
{
/**
* Dashboard assemblee
*/
public function index()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$assemblee = Assemblea::with(['stabile', 'creatoDa'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->orderBy('data_prima_convocazione', 'desc')
->paginate(15);
// Statistiche
$stats = [
'assemblee_programmate' => Assemblea::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->whereIn('stato', ['bozza', 'convocata'])->count(),
'assemblee_svolte' => Assemblea::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('stato', 'svolta')->count(),
'convocazioni_inviate' => Convocazione::whereHas('assemblea.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('data_invio', '>=', now()->subDays(30))->count(),
'delibere_approvate' => OrdineGiorno::whereHas('assemblea.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('esito_votazione', 'approvato')->count(),
];
return view('admin.assemblee.index', compact('assemblee', 'stats'));
}
/**
* Form creazione assemblea
*/
public function create()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get();
return view('admin.assemblee.create', compact('stabili'));
}
/**
* Store assemblea
*/
public function store(Request $request)
{
$request->validate([
'stabile_id' => 'required|exists:stabili,id_stabile',
'tipo' => 'required|in:ordinaria,straordinaria',
'data_prima_convocazione' => 'required|date|after:now',
'data_seconda_convocazione' => 'required|date|after:data_prima_convocazione',
'luogo' => 'required|string|max:255',
'note' => 'nullable|string',
'ordine_giorno' => 'required|array|min:1',
'ordine_giorno.*.titolo' => 'required|string|max:255',
'ordine_giorno.*.descrizione' => 'required|string',
'ordine_giorno.*.tipo_voce' => 'required|in:discussione,delibera,spesa,preventivo,altro',
'ordine_giorno.*.importo_spesa' => 'nullable|numeric|min:0',
'ordine_giorno.*.tabella_millesimale_id' => 'nullable|exists:tabelle_millesimali,id',
]);
DB::beginTransaction();
try {
$assemblea = Assemblea::create([
'stabile_id' => $request->stabile_id,
'tipo' => $request->tipo,
'data_prima_convocazione' => $request->data_prima_convocazione,
'data_seconda_convocazione' => $request->data_seconda_convocazione,
'luogo' => $request->luogo,
'note' => $request->note,
'stato' => 'bozza',
'creato_da_user_id' => Auth::id(),
]);
// Crea ordine del giorno
foreach ($request->ordine_giorno as $index => $punto) {
OrdineGiorno::create([
'assemblea_id' => $assemblea->id,
'numero_punto' => $index + 1,
'titolo' => $punto['titolo'],
'descrizione' => $punto['descrizione'],
'tipo_voce' => $punto['tipo_voce'],
'importo_spesa' => $punto['importo_spesa'] ?? null,
'tabella_millesimale_id' => $punto['tabella_millesimale_id'] ?? null,
]);
}
DB::commit();
return redirect()->route('admin.assemblee.show', $assemblea)
->with('success', 'Assemblea creata con successo.');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]);
}
}
/**
* Visualizza assemblea
*/
public function show(Assemblea $assemblea)
{
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
$assemblea->load([
'stabile',
'ordineGiorno.preventivo',
'ordineGiorno.tabellaMillesimale',
'convocazioni.soggetto',
'presenze.soggetto',
'verbale',
'documenti'
]);
// Calcola statistiche convocazioni
$statsConvocazioni = [
'totale_inviate' => $assemblea->convocazioni->count(),
'consegnate' => $assemblea->convocazioni->where('esito_invio', 'consegnato')->count(),
'lette' => $assemblea->convocazioni->where('esito_invio', 'letto')->count(),
'conferme_presenza' => $assemblea->convocazioni->where('presenza_confermata', true)->count(),
'deleghe' => $assemblea->convocazioni->where('delega_presente', true)->count(),
];
// Calcola quorum se assemblea svolta
$quorum = null;
if ($assemblea->stato === 'svolta') {
$quorum = $assemblea->calcolaQuorum();
}
return view('admin.assemblee.show', compact('assemblea', 'statsConvocazioni', 'quorum'));
}
/**
* Invia convocazioni
*/
public function inviaConvocazioni(Request $request, Assemblea $assemblea)
{
$request->validate([
'canali' => 'required|array',
'canali.*' => 'in:email,pec,whatsapp,telegram,raccomandata,mano,portiere',
]);
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
if ($assemblea->stato !== 'bozza') {
return back()->withErrors(['error' => 'Le convocazioni possono essere inviate solo per assemblee in bozza.']);
}
try {
$convocazioniInviate = $assemblea->inviaConvocazioni($request->canali, Auth::id());
return back()->with('success', "Inviate {$convocazioniInviate} convocazioni con successo.");
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore nell\'invio convocazioni: ' . $e->getMessage()]);
}
}
/**
* Gestione presenze
*/
public function presenze(Assemblea $assemblea)
{
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
$unitaImmobiliari = $assemblea->stabile->unitaImmobiliari()
->with(['proprieta.soggetto'])
->get();
$presenzeEsistenti = $assemblea->presenze()
->with(['soggetto', 'unitaImmobiliare'])
->get()
->keyBy(function($presenza) {
return $presenza->soggetto_id . '_' . $presenza->unita_immobiliare_id;
});
return view('admin.assemblee.presenze', compact('assemblea', 'unitaImmobiliari', 'presenzeEsistenti'));
}
/**
* Registra presenza
*/
public function registraPresenza(Request $request, Assemblea $assemblea)
{
$request->validate([
'presenze' => 'required|array',
'presenze.*.soggetto_id' => 'required|exists:soggetti,id_soggetto',
'presenze.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id_unita',
'presenze.*.tipo_presenza' => 'required|in:presente,delegato,assente',
'presenze.*.millesimi_rappresentati' => 'required|numeric|min:0',
'presenze.*.delegante_soggetto_id' => 'nullable|exists:soggetti,id_soggetto',
]);
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
DB::beginTransaction();
try {
// Elimina presenze esistenti
$assemblea->presenze()->delete();
// Registra nuove presenze
foreach ($request->presenze as $presenzaData) {
if ($presenzaData['tipo_presenza'] !== 'assente') {
PresenzaAssemblea::create([
'assemblea_id' => $assemblea->id,
'soggetto_id' => $presenzaData['soggetto_id'],
'unita_immobiliare_id' => $presenzaData['unita_immobiliare_id'],
'tipo_presenza' => $presenzaData['tipo_presenza'],
'millesimi_rappresentati' => $presenzaData['millesimi_rappresentati'],
'delegante_soggetto_id' => $presenzaData['delegante_soggetto_id'] ?? null,
'ora_arrivo' => now(),
]);
}
}
// Aggiorna stato assemblea
$assemblea->update(['stato' => 'svolta', 'data_svolgimento' => now()]);
DB::commit();
return back()->with('success', 'Presenze registrate con successo.');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore nella registrazione presenze: ' . $e->getMessage()]);
}
}
/**
* Gestione votazioni
*/
public function votazioni(Assemblea $assemblea, OrdineGiorno $ordineGiorno)
{
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
if ($assemblea->stato !== 'svolta') {
return back()->withErrors(['error' => 'Le votazioni possono essere gestite solo per assemblee svolte.']);
}
$presenze = $assemblea->presenze()->with(['soggetto', 'unitaImmobiliare'])->get();
$votazioniEsistenti = $ordineGiorno->votazioni()
->get()
->keyBy(function($voto) {
return $voto->soggetto_id . '_' . $voto->unita_immobiliare_id;
});
return view('admin.assemblee.votazioni', compact('assemblea', 'ordineGiorno', 'presenze', 'votazioniEsistenti'));
}
/**
* Registra votazioni
*/
public function registraVotazioni(Request $request, Assemblea $assemblea, OrdineGiorno $ordineGiorno)
{
$request->validate([
'voti' => 'required|array',
'voti.*.soggetto_id' => 'required|exists:soggetti,id_soggetto',
'voti.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id_unita',
'voti.*.voto' => 'required|in:favorevole,contrario,astenuto,non_votante',
'voti.*.millesimi_voto' => 'required|numeric|min:0',
]);
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
DB::beginTransaction();
try {
// Elimina votazioni esistenti
$ordineGiorno->votazioni()->delete();
// Registra nuovi voti
foreach ($request->voti as $votoData) {
if ($votoData['voto'] !== 'non_votante') {
Votazione::create([
'ordine_giorno_id' => $ordineGiorno->id,
'soggetto_id' => $votoData['soggetto_id'],
'unita_immobiliare_id' => $votoData['unita_immobiliare_id'],
'voto' => $votoData['voto'],
'millesimi_voto' => $votoData['millesimi_voto'],
'data_voto' => now(),
]);
}
}
// Calcola risultato
$risultato = $ordineGiorno->calcolaRisultato();
DB::commit();
return back()->with('success', 'Votazioni registrate. Esito: ' . $risultato['esito']);
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore nella registrazione voti: ' . $e->getMessage()]);
}
}
/**
* Gestione verbale
*/
public function verbale(Assemblea $assemblea)
{
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
$assemblea->load(['ordineGiorno.delibera', 'presenze.soggetto']);
$verbale = $assemblea->verbale;
return view('admin.assemblee.verbale', compact('assemblea', 'verbale'));
}
/**
* Store/Update verbale
*/
public function storeVerbale(Request $request, Assemblea $assemblea)
{
$request->validate([
'testo_verbale' => 'required|string',
'allegati.*' => 'nullable|file|max:10240',
]);
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
try {
$numeroVerbale = $this->generaNumeroVerbale($assemblea);
// Gestione allegati
$allegati = [];
if ($request->hasFile('allegati')) {
foreach ($request->file('allegati') as $file) {
$path = $file->store('verbali/allegati', 'public');
$allegati[] = [
'nome' => $file->getClientOriginalName(),
'path' => $path,
'size' => $file->getSize(),
];
}
}
$verbale = Verbale::updateOrCreate(
['assemblea_id' => $assemblea->id],
[
'numero_verbale' => $numeroVerbale,
'testo_verbale' => $request->testo_verbale,
'allegati' => $allegati,
'data_redazione' => now(),
'redatto_da_user_id' => Auth::id(),
'stato' => 'definitivo',
]
);
return back()->with('success', 'Verbale salvato con successo.');
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore nel salvataggio verbale: ' . $e->getMessage()]);
}
}
/**
* Invia verbale ai condomini
*/
public function inviaVerbale(Request $request, Assemblea $assemblea)
{
// Verifica accesso
if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
$verbale = $assemblea->verbale;
if (!$verbale) {
return back()->withErrors(['error' => 'Nessun verbale da inviare.']);
}
try {
// Invia verbale a tutti i condomini
$inviiRiusciti = $this->inviaVerbaleCondomini($assemblea, $verbale);
$verbale->update([
'inviato_condomini' => true,
'data_invio_condomini' => now(),
'stato' => 'inviato',
]);
return back()->with('success', "Verbale inviato a {$inviiRiusciti} condomini.");
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore nell\'invio verbale: ' . $e->getMessage()]);
}
}
/**
* Registro protocollo
*/
public function registroProtocollo(Request $request)
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$query = RegistroProtocollo::with(['assemblea.stabile', 'soggettoDestinatario', 'creatoDa'])
->whereHas('assemblea.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
});
// Filtri
if ($request->filled('tipo_comunicazione')) {
$query->where('tipo_comunicazione', $request->tipo_comunicazione);
}
if ($request->filled('data_da')) {
$query->where('data_invio', '>=', $request->data_da);
}
if ($request->filled('data_a')) {
$query->where('data_invio', '<=', $request->data_a);
}
$comunicazioni = $query->orderBy('data_invio', 'desc')->paginate(20);
return view('admin.assemblee.registro-protocollo', compact('comunicazioni'));
}
/**
* Genera numero verbale
*/
private function generaNumeroVerbale(Assemblea $assemblea)
{
$anno = $assemblea->data_prima_convocazione->year;
$ultimoVerbale = Verbale::whereHas('assemblea', function($q) use ($anno) {
$q->whereYear('data_prima_convocazione', $anno);
})->orderBy('numero_verbale', 'desc')->first();
if ($ultimoVerbale) {
$numero = intval(substr($ultimoVerbale->numero_verbale, -3)) + 1;
} else {
$numero = 1;
}
return 'VERB/' . $anno . '/' . str_pad($numero, 3, '0', STR_PAD_LEFT);
}
/**
* Invia verbale ai condomini
*/
private function inviaVerbaleCondomini(Assemblea $assemblea, Verbale $verbale)
{
$unitaImmobiliari = $assemblea->stabile->unitaImmobiliari()->with('proprieta.soggetto')->get();
$inviiRiusciti = 0;
foreach ($unitaImmobiliari as $unita) {
foreach ($unita->proprieta as $proprieta) {
$soggetto = $proprieta->soggetto;
if ($soggetto->email) {
// Simula invio email
$numeroProtocollo = RegistroProtocollo::generaNumeroProtocollo();
RegistroProtocollo::create([
'numero_protocollo' => $numeroProtocollo,
'tipo_comunicazione' => 'verbale',
'assemblea_id' => $assemblea->id,
'soggetto_destinatario_id' => $soggetto->id_soggetto,
'oggetto' => "Verbale Assemblea {$assemblea->tipo} del {$assemblea->data_prima_convocazione->format('d/m/Y')}",
'contenuto' => $verbale->testo_verbale,
'canale' => 'email',
'data_invio' => now(),
'esito' => 'inviato',
'creato_da_user_id' => Auth::id(),
]);
$inviiRiusciti++;
}
}
}
return $inviiRiusciti;
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Banca;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class BancaController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$banche = Banca::with(['movimentiBancari'])
->orderBy('denominazione')
->paginate(15);
return view('admin.banche.index', compact('banche'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.banche.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'denominazione' => 'required|string|max:255',
'codice_abi' => 'nullable|string|max:10',
'codice_cab' => 'nullable|string|max:10',
'iban' => 'nullable|string|max:34',
'indirizzo' => 'nullable|string',
'telefono' => 'nullable|string|max:20',
'email' => 'nullable|email|max:255',
'note' => 'nullable|string',
'attivo' => 'boolean',
]);
$banca = Banca::create($validated);
return redirect()
->route('admin.banche.index')
->with('success', 'Banca creata con successo.');
}
/**
* Display the specified resource.
*/
public function show(Banca $banca)
{
$banca->load(['movimentiBancari' => function($query) {
$query->orderBy('data_operazione', 'desc')->limit(10);
}]);
return view('admin.banche.show', compact('banca'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Banca $banca)
{
return view('admin.banche.edit', compact('banca'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Banca $banca)
{
$validated = $request->validate([
'denominazione' => 'required|string|max:255',
'codice_abi' => 'nullable|string|max:10',
'codice_cab' => 'nullable|string|max:10',
'iban' => 'nullable|string|max:34',
'indirizzo' => 'nullable|string',
'telefono' => 'nullable|string|max:20',
'email' => 'nullable|email|max:255',
'note' => 'nullable|string',
'attivo' => 'boolean',
]);
$banca->update($validated);
return redirect()
->route('admin.banche.index')
->with('success', 'Banca aggiornata con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Banca $banca)
{
// Check if bank has any movements before deleting
if ($banca->movimentiBancari()->count() > 0) {
return redirect()
->route('admin.banche.index')
->with('error', 'Impossibile eliminare la banca: sono presenti movimenti associati.');
}
$banca->delete();
return redirect()
->route('admin.banche.index')
->with('success', 'Banca eliminata con successo.');
}
}

View File

@ -0,0 +1,386 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Bilancio;
use App\Models\ScritturaBilancio;
use App\Models\Conguaglio;
use App\Models\Quadratura;
use App\Models\Stabile;
use App\Models\Gestione;
use App\Models\PianoConto;
use App\Models\MovimentoContabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class BilancioController extends Controller
{
/**
* Dashboard bilanci
*/
public function index()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$bilanci = Bilancio::with(['stabile', 'gestione', 'approvatoDa'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->orderBy('anno_esercizio', 'desc')
->orderBy('created_at', 'desc')
->paginate(15);
// Statistiche
$stats = [
'bilanci_aperti' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->whereIn('stato', ['bozza', 'provvisorio'])->count(),
'bilanci_approvati' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('stato', 'approvato')->count(),
'conguagli_da_pagare' => Conguaglio::whereHas('bilancio.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('stato', 'calcolato')->count(),
'totale_avanzi' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('risultato_gestione', '>', 0)->sum('risultato_gestione'),
];
return view('admin.bilanci.index', compact('bilanci', 'stats'));
}
/**
* Form creazione bilancio
*/
public function create()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get();
return view('admin.bilanci.create', compact('stabili'));
}
/**
* Store bilancio
*/
public function store(Request $request)
{
$request->validate([
'stabile_id' => 'required|exists:stabili,id_stabile',
'gestione_id' => 'required|exists:gestioni,id_gestione',
'anno_esercizio' => 'required|integer|min:2020|max:2030',
'data_inizio_esercizio' => 'required|date',
'data_fine_esercizio' => 'required|date|after:data_inizio_esercizio',
'tipo_gestione' => 'required|in:ordinaria,riscaldamento,straordinaria,acqua,altro',
'descrizione' => 'required|string|max:255',
]);
DB::beginTransaction();
try {
$bilancio = Bilancio::create([
'stabile_id' => $request->stabile_id,
'gestione_id' => $request->gestione_id,
'anno_esercizio' => $request->anno_esercizio,
'data_inizio_esercizio' => $request->data_inizio_esercizio,
'data_fine_esercizio' => $request->data_fine_esercizio,
'tipo_gestione' => $request->tipo_gestione,
'descrizione' => $request->descrizione,
'stato' => 'bozza',
'versione' => 1,
]);
// Importa movimenti contabili del periodo
$this->importaMovimentiContabili($bilancio);
DB::commit();
return redirect()->route('admin.bilanci.show', $bilancio)
->with('success', 'Bilancio creato con successo.');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]);
}
}
/**
* Visualizza bilancio
*/
public function show(Bilancio $bilancio)
{
// Verifica accesso
if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
$bilancio->load([
'stabile',
'gestione',
'scritture.dettagli.conto',
'conguagli.unitaImmobiliare',
'quadrature',
'rimborsiAssicurativi'
]);
// Calcola totali aggiornati
$bilancio->calcolaTotali();
return view('admin.bilanci.show', compact('bilancio'));
}
/**
* Importa movimenti contabili nel bilancio
*/
private function importaMovimentiContabili(Bilancio $bilancio)
{
$movimenti = MovimentoContabile::where('stabile_id', $bilancio->stabile_id)
->where('gestione_id', $bilancio->gestione_id)
->whereBetween('data_registrazione', [
$bilancio->data_inizio_esercizio,
$bilancio->data_fine_esercizio
])
->with('dettagli')
->get();
foreach ($movimenti as $movimento) {
$this->creaScritturaDaMovimento($bilancio, $movimento);
}
}
/**
* Crea scrittura bilancio da movimento contabile
*/
private function creaScritturaDaMovimento(Bilancio $bilancio, MovimentoContabile $movimento)
{
$scrittura = ScritturaBilancio::create([
'bilancio_id' => $bilancio->id,
'numero_scrittura' => $this->generaNumeroScrittura($bilancio),
'data_scrittura' => $movimento->data_registrazione,
'descrizione' => $movimento->descrizione,
'tipo_scrittura' => 'gestione',
'importo_totale' => $movimento->importo_totale,
'movimento_contabile_id' => $movimento->id,
'creato_da_user_id' => Auth::id(),
]);
// Crea dettagli in partita doppia
foreach ($movimento->dettagli as $dettaglio) {
$scrittura->dettagli()->create([
'conto_id' => $dettaglio->conto_id ?? $this->getContoDefault($movimento->tipo_movimento),
'importo_dare' => $dettaglio->importo_dare,
'importo_avere' => $dettaglio->importo_avere,
'descrizione_dettaglio' => $dettaglio->descrizione,
]);
}
return $scrittura;
}
/**
* Calcola conguagli
*/
public function calcolaConguagli(Bilancio $bilancio)
{
// Verifica accesso
if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
DB::beginTransaction();
try {
$bilancio->calcolaConguagli();
DB::commit();
return back()->with('success', 'Conguagli calcolati con successo.');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore nel calcolo conguagli: ' . $e->getMessage()]);
}
}
/**
* Genera rate conguaglio
*/
public function generaRateConguaglio(Request $request, Bilancio $bilancio)
{
$request->validate([
'conguaglio_ids' => 'required|array',
'numero_rate' => 'required|integer|min:1|max:12',
'data_inizio' => 'required|date',
]);
// Verifica accesso
if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
DB::beginTransaction();
try {
$dataInizio = Carbon::parse($request->data_inizio);
$rateGenerate = 0;
foreach ($request->conguaglio_ids as $conguaglioId) {
$conguaglio = Conguaglio::findOrFail($conguaglioId);
if ($conguaglio->bilancio_id !== $bilancio->id) {
continue;
}
$rate = $conguaglio->generaRate($request->numero_rate, $dataInizio, Auth::id());
$rateGenerate += $rate->count();
}
DB::commit();
return back()->with('success', "Generate {$rateGenerate} rate di conguaglio.");
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore nella generazione rate: ' . $e->getMessage()]);
}
}
/**
* Quadratura bilancio
*/
public function quadratura(Request $request, Bilancio $bilancio)
{
$request->validate([
'data_quadratura' => 'required|date',
'saldo_banca_effettivo' => 'required|numeric',
]);
// Verifica accesso
if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
// Calcola saldo contabile
$saldoContabile = $this->calcolaSaldoContabile($bilancio, $request->data_quadratura);
// Calcola totali crediti/debiti
$totaleCrediti = $bilancio->conguagli()->where('tipo_conguaglio', 'a_credito')->sum('conguaglio_dovuto');
$totaleDebiti = $bilancio->conguagli()->where('tipo_conguaglio', 'a_debito')->sum('conguaglio_dovuto');
// Calcola rate
$totaleRateEmesse = $this->calcolaTotaleRateEmesse($bilancio);
$totaleRateIncassate = $this->calcolaTotaleRateIncassate($bilancio);
$differenza = $request->saldo_banca_effettivo - $saldoContabile;
$quadratura = Quadratura::create([
'bilancio_id' => $bilancio->id,
'data_quadratura' => $request->data_quadratura,
'saldo_banca_effettivo' => $request->saldo_banca_effettivo,
'saldo_contabile_calcolato' => $saldoContabile,
'differenza' => $differenza,
'totale_crediti_condomini' => $totaleCrediti,
'totale_debiti_condomini' => $totaleDebiti,
'totale_rate_emesse' => $totaleRateEmesse,
'totale_rate_incassate' => $totaleRateIncassate,
'quadratura_ok' => abs($differenza) < 0.01,
'verificato_da_user_id' => Auth::id(),
]);
return back()->with('success', 'Quadratura eseguita con successo.');
}
/**
* Chiusura esercizio
*/
public function chiusuraEsercizio(Request $request, Bilancio $bilancio)
{
$request->validate([
'motivo_chiusura' => 'required|string',
]);
// Verifica accesso e stato
if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
if ($bilancio->stato !== 'approvato') {
return back()->withErrors(['error' => 'Il bilancio deve essere approvato prima della chiusura.']);
}
DB::beginTransaction();
try {
// Genera scritture di chiusura
$bilancio->generaScritture_ChiusuraEsercizio(Auth::id());
// Aggiorna stato bilancio
$bilancio->update([
'stato' => 'chiuso',
'data_chiusura' => now(),
'chiuso_da_user_id' => Auth::id(),
]);
DB::commit();
return back()->with('success', 'Esercizio chiuso con successo.');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore nella chiusura: ' . $e->getMessage()]);
}
}
/**
* Calcola saldo contabile alla data
*/
private function calcolaSaldoContabile(Bilancio $bilancio, $data)
{
// Implementazione calcolo saldo contabile
return $bilancio->totale_entrate - $bilancio->totale_uscite;
}
/**
* Calcola totale rate emesse
*/
private function calcolaTotaleRateEmesse(Bilancio $bilancio)
{
// Implementazione calcolo rate emesse
return 0; // Placeholder
}
/**
* Calcola totale rate incassate
*/
private function calcolaTotaleRateIncassate(Bilancio $bilancio)
{
// Implementazione calcolo rate incassate
return 0; // Placeholder
}
/**
* Genera numero scrittura
*/
private function generaNumeroScrittura(Bilancio $bilancio)
{
$ultimaScrittura = ScritturaBilancio::where('bilancio_id', $bilancio->id)
->orderBy('numero_scrittura', 'desc')
->first();
if ($ultimaScrittura) {
$numero = intval(substr($ultimaScrittura->numero_scrittura, -4)) + 1;
} else {
$numero = 1;
}
return 'SCR/' . $bilancio->anno_esercizio . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT);
}
/**
* Get conto default per tipo movimento
*/
private function getContoDefault($tipoMovimento)
{
// Implementazione per ottenere conto default
return 1; // Placeholder
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\MovimentoContabile;
use App\Models\Gestione;
use App\Models\Stabile;
use App\Models\Fornitore;
use App\Models\VoceSpesa;
use App\Models\TabellaMillesimale;
use App\Models\Documento;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class ContabilitaController extends Controller
{
/**
* Dashboard contabilità
*/
public function index()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
// Statistiche generali
$stats = [
'movimenti_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->whereMonth('data_registrazione', now()->month)->count(),
'importo_entrate_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('tipo_movimento', 'entrata')
->whereMonth('data_registrazione', now()->month)
->sum('importo_totale'),
'importo_uscite_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('tipo_movimento', 'uscita')
->whereMonth('data_registrazione', now()->month)
->sum('importo_totale'),
];
// Ultimi movimenti
$ultimiMovimenti = MovimentoContabile::with(['stabile', 'gestione', 'fornitore'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->orderBy('created_at', 'desc')
->take(10)
->get();
return view('admin.contabilita.index', compact('stats', 'ultimiMovimenti'));
}
/**
* Lista movimenti contabili
*/
public function movimenti(Request $request)
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$query = MovimentoContabile::with(['stabile', 'gestione', 'fornitore'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
});
// Filtri
if ($request->filled('stabile_id')) {
$query->where('stabile_id', $request->stabile_id);
}
if ($request->filled('gestione_id')) {
$query->where('gestione_id', $request->gestione_id);
}
if ($request->filled('tipo_movimento')) {
$query->where('tipo_movimento', $request->tipo_movimento);
}
if ($request->filled('data_da')) {
$query->where('data_registrazione', '>=', $request->data_da);
}
if ($request->filled('data_a')) {
$query->where('data_registrazione', '<=', $request->data_a);
}
$movimenti = $query->orderBy('data_registrazione', 'desc')->paginate(20);
// Dati per i filtri
$stabili = Stabile::where('amministratore_id', $amministratore_id)->get();
$gestioni = Gestione::whereIn('stabile_id', $stabili->pluck('id_stabile'))->get();
return view('admin.contabilita.movimenti', compact('movimenti', 'stabili', 'gestioni'));
}
/**
* Form registrazione movimento
*/
public function registrazione()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get();
$fornitori = Fornitore::where('amministratore_id', $amministratore_id)->get();
return view('admin.contabilita.registrazione', compact('stabili', 'fornitori'));
}
/**
* Store registrazione movimento
*/
public function storeRegistrazione(Request $request)
{
$request->validate([
'stabile_id' => 'required|exists:stabili,id_stabile',
'gestione_id' => 'required|exists:gestioni,id_gestione',
'tipo_movimento' => 'required|in:entrata,uscita',
'data_documento' => 'required|date',
'numero_documento' => 'required|string|max:50',
'descrizione' => 'required|string|max:255',
'importo_totale' => 'required|numeric|min:0',
'fornitore_id' => 'nullable|exists:fornitori,id_fornitore',
'ritenuta_acconto' => 'nullable|numeric|min:0',
'dettagli' => 'required|array|min:1',
'dettagli.*.voce_spesa_id' => 'required|exists:voci_spesa,id',
'dettagli.*.importo' => 'required|numeric|min:0',
'dettagli.*.tabella_millesimale_id' => 'nullable|exists:tabelle_millesimali,id',
]);
DB::beginTransaction();
try {
// Genera protocollo univoco
$protocollo = $this->generaProtocollo($request->stabile_id);
// Crea movimento principale
$movimento = MovimentoContabile::create([
'stabile_id' => $request->stabile_id,
'gestione_id' => $request->gestione_id,
'fornitore_id' => $request->fornitore_id,
'protocollo' => $protocollo,
'data_registrazione' => now(),
'data_documento' => $request->data_documento,
'numero_documento' => $request->numero_documento,
'descrizione' => $request->descrizione,
'tipo_movimento' => $request->tipo_movimento,
'importo_totale' => $request->importo_totale,
'ritenuta_acconto' => $request->ritenuta_acconto ?? 0,
'importo_netto' => $request->importo_totale - ($request->ritenuta_acconto ?? 0),
'stato' => 'registrato',
]);
// Crea dettagli movimento (partita doppia)
foreach ($request->dettagli as $dettaglio) {
$movimento->dettagli()->create([
'voce_spesa_id' => $dettaglio['voce_spesa_id'],
'tabella_millesimale_id' => $dettaglio['tabella_millesimale_id'] ?? null,
'descrizione' => $dettaglio['descrizione'] ?? '',
'importo_dare' => $request->tipo_movimento === 'uscita' ? $dettaglio['importo'] : 0,
'importo_avere' => $request->tipo_movimento === 'entrata' ? $dettaglio['importo'] : 0,
]);
}
DB::commit();
return redirect()->route('admin.contabilita.movimenti')
->with('success', 'Movimento registrato con successo. Protocollo: ' . $protocollo);
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Errore durante la registrazione: ' . $e->getMessage()]);
}
}
/**
* Import da XML (Fattura Elettronica)
*/
public function importXml(Request $request)
{
$request->validate([
'xml_file' => 'required|file|mimes:xml|max:2048',
'stabile_id' => 'required|exists:stabili,id_stabile',
]);
try {
$xmlContent = file_get_contents($request->file('xml_file')->path());
$xml = simplexml_load_string($xmlContent);
// Parsing XML fattura elettronica
$fatturaData = $this->parseXmlFattura($xml);
// Salva documento
$documento = Documento::create([
'documentable_type' => Stabile::class,
'documentable_id' => $request->stabile_id,
'nome_file' => $request->file('xml_file')->getClientOriginalName(),
'path_file' => $request->file('xml_file')->store('documenti/xml'),
'tipo_documento' => 'fattura_elettronica',
'xml_data' => $fatturaData,
'mime_type' => 'application/xml',
'dimensione_file' => $request->file('xml_file')->getSize(),
]);
return view('admin.contabilita.import-xml-review', compact('fatturaData', 'documento'));
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore durante l\'importazione XML: ' . $e->getMessage()]);
}
}
/**
* Genera protocollo univoco
*/
private function generaProtocollo($stabile_id)
{
$anno = date('Y');
$ultimoProtocollo = MovimentoContabile::where('stabile_id', $stabile_id)
->whereYear('data_registrazione', $anno)
->max('protocollo');
if ($ultimoProtocollo) {
$numero = intval(substr($ultimoProtocollo, -4)) + 1;
} else {
$numero = 1;
}
return $stabile_id . '/' . $anno . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT);
}
/**
* Parse XML Fattura Elettronica
*/
private function parseXmlFattura($xml)
{
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('fe', $ns['']);
// Dati generali fattura
$datiGenerali = [
'numero' => (string) $xml->xpath('//DatiGeneraliDocumento/Numero')[0] ?? '',
'data' => (string) $xml->xpath('//DatiGeneraliDocumento/Data')[0] ?? '',
'importo_totale' => (float) $xml->xpath('//DatiGeneraliDocumento/ImportoTotaleDocumento')[0] ?? 0,
];
// Dati fornitore
$fornitore = [
'denominazione' => (string) $xml->xpath('//CedentePrestatore//Denominazione')[0] ?? '',
'partita_iva' => (string) $xml->xpath('//CedentePrestatore//IdFiscaleIVA/IdCodice')[0] ?? '',
'codice_fiscale' => (string) $xml->xpath('//CedentePrestatore//CodiceFiscale')[0] ?? '',
];
// Righe fattura
$righe = [];
$dettaglioLinee = $xml->xpath('//DettaglioLinee');
foreach ($dettaglioLinee as $linea) {
$righe[] = [
'descrizione' => (string) $linea->Descrizione ?? '',
'quantita' => (float) $linea->Quantita ?? 1,
'prezzo_unitario' => (float) $linea->PrezzoUnitario ?? 0,
'importo_totale' => (float) $linea->PrezzoTotale ?? 0,
];
}
return [
'dati_generali' => $datiGenerali,
'fornitore' => $fornitore,
'righe' => $righe,
];
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ContrattoLocazioneController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('admin.contratti-locazione.index', [
'title' => 'Contratti Locazione',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Contratti Locazione' => ''
]
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.contratti-locazione.create', [
'title' => 'Nuovo Contratto Locazione',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Contratti Locazione' => route('admin.contratti-locazione.index'),
'Nuovo Contratto' => ''
]
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// TODO: Implement store logic
return redirect()->route('admin.contratti-locazione.index')
->with('success', 'Contratto locazione creato con successo.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
return view('admin.contratti-locazione.show', [
'title' => 'Dettaglio Contratto Locazione',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Contratti Locazione' => route('admin.contratti-locazione.index'),
'Dettaglio' => ''
]
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
return view('admin.contratti-locazione.edit', [
'title' => 'Modifica Contratto Locazione',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Contratti Locazione' => route('admin.contratti-locazione.index'),
'Modifica' => ''
]
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// TODO: Implement update logic
return redirect()->route('admin.contratti-locazione.index')
->with('success', 'Contratto locazione aggiornato con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
// TODO: Implement destroy logic
return redirect()->route('admin.contratti-locazione.index')
->with('success', 'Contratto locazione eliminato con successo.');
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Stabile;
use App\Models\Ticket;
use App\Models\Rata;
use App\Models\Documento;
use App\Models\MovimentoContabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class DashboardController extends Controller
{
public function index()
{
// Ottieni i dati reali dalle statistiche
$statsController = new DashboardStatsController();
$statsResponse = $statsController->getStats();
$stats = $statsResponse->getData(true);
// Ottieni l'ID dell'amministratore in modo sicuro
$user = Auth::user();
$amministratore_id = null;
if ($user && $user->amministratore) {
$amministratore_id = $user->amministratore->id; // Usa 'id' invece di 'id_amministratore'
}
// Se l'utente non ha un amministratore associato, mostra vista vuota
if (!$amministratore_id) {
return view('admin.dashboard', [
'stats' => $stats,
'ticketsAperti' => collect(),
'scadenzeImminenti' => collect(),
'ultimiDocumenti' => collect(),
'ultimiMovimenti' => collect(),
]);
}
// Statistiche principali
$stats = [
'stabili_gestiti' => Stabile::where('amministratore_id', $amministratore_id)->count(),
'stabili_attivi' => Stabile::where('amministratore_id', $amministratore_id)->where('stato', 'attivo')->count(),
'ticket_aperti' => Ticket::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(),
'ticket_urgenti' => Ticket::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico'])->count(),
];
// Ticket aperti da lavorare
$ticketsAperti = Ticket::with(['stabile', 'categoriaTicket'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])
->orderBy('priorita', 'desc')
->orderBy('created_at', 'desc')
->take(5)
->get();
// Scadenze imminenti (prossimi 30 giorni)
$scadenzeImminenti = collect(); // Placeholder per quando implementeremo le rate
// Ultimi documenti caricati
$ultimiDocumenti = Documento::with('documentable')
->whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->orderBy('created_at', 'desc')
->take(5)
->get();
// Movimenti contabili recenti
$ultimiMovimenti = MovimentoContabile::with(['stabile', 'fornitore'])
->whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})
->orderBy('created_at', 'desc')
->take(5)
->get();
return view('admin.dashboard', compact(
'stats',
'ticketsAperti',
'scadenzeImminenti',
'ultimiDocumenti',
'ultimiMovimenti'
));
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class DashboardStatsController extends Controller
{
/**
* Ottiene le statistiche per la dashboard
*/
public function getStats()
{
try {
// Statistiche base (con fallback se le tabelle non esistono)
$stats = [
'stabili_totali' => $this->getStabiliCount(),
'condomini_totali' => $this->getCondominiCount(),
'tickets_aperti' => $this->getTicketsApertiCount(),
'fatture_mese' => $this->getFattureMeseSum(),
'notifiche_recenti' => $this->getNotificheRecenti(),
'ultimi_tickets' => $this->getUltimiTickets()
];
return response()->json($stats);
} catch (\Exception $e) {
// Se ci sono errori, restituiamo dati mock
return response()->json([
'stabili_totali' => 12,
'condomini_totali' => 248,
'tickets_aperti' => 7,
'fatture_mese' => 15420.00,
'notifiche_recenti' => [
['tipo' => 'warning', 'messaggio' => 'Scadenza rata - Condominio Roma'],
['tipo' => 'info', 'messaggio' => 'Nuovo ticket supporto #1234'],
['tipo' => 'success', 'messaggio' => 'Fattura #2024-001 pagata']
],
'ultimi_tickets' => [
['id' => '#1234', 'descrizione' => 'Problema ascensore', 'stato' => 'Aperto'],
['id' => '#1233', 'descrizione' => 'Perdita idrica', 'stato' => 'Urgente'],
['id' => '#1232', 'descrizione' => 'Richiesta info', 'stato' => 'Risolto']
]
]);
}
}
private function getStabiliCount()
{
try {
if (DB::getSchemaBuilder()->hasTable('stabili')) {
return DB::table('stabili')->count();
}
} catch (\Exception $e) {}
return 12; // fallback
}
private function getCondominiCount()
{
try {
if (DB::getSchemaBuilder()->hasTable('soggetti')) {
return DB::table('soggetti')->where('tipo', 'condomino')->count();
}
} catch (\Exception $e) {}
return 248; // fallback
}
private function getTicketsApertiCount()
{
try {
if (DB::getSchemaBuilder()->hasTable('tickets')) {
return DB::table('tickets')->where('stato', 'aperto')->count();
}
} catch (\Exception $e) {}
return 7; // fallback
}
private function getFattureMeseSum()
{
try {
if (DB::getSchemaBuilder()->hasTable('fatture')) {
return DB::table('fatture')
->whereMonth('created_at', now()->month)
->whereYear('created_at', now()->year)
->sum('importo') ?? 0;
}
} catch (\Exception $e) {}
return 15420.00; // fallback
}
private function getNotificheRecenti()
{
// Per ora restituiamo dati mock, poi implementeremo una tabella notifiche
return [
['tipo' => 'warning', 'messaggio' => 'Scadenza rata - Condominio Roma'],
['tipo' => 'info', 'messaggio' => 'Nuovo ticket supporto #1234'],
['tipo' => 'success', 'messaggio' => 'Fattura #2024-001 pagata']
];
}
private function getUltimiTickets()
{
try {
if (DB::getSchemaBuilder()->hasTable('tickets')) {
return DB::table('tickets')
->select('id', 'oggetto as descrizione', 'stato')
->orderBy('created_at', 'desc')
->limit(3)
->get()
->toArray();
}
} catch (\Exception $e) {}
return [
['id' => '#1234', 'descrizione' => 'Problema ascensore', 'stato' => 'Aperto'],
['id' => '#1233', 'descrizione' => 'Perdita idrica', 'stato' => 'Urgente'],
['id' => '#1232', 'descrizione' => 'Richiesta info', 'stato' => 'Risolto']
];
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DirittoRealeController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('admin.diritti-reali.index', [
'title' => 'Diritti Reali',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Diritti Reali' => ''
]
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('admin.diritti-reali.create', [
'title' => 'Nuovo Diritto Reale',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Diritti Reali' => route('admin.diritti-reali.index'),
'Nuovo Diritto' => ''
]
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
// TODO: Implement store logic
return redirect()->route('admin.diritti-reali.index')
->with('success', 'Diritto reale creato con successo.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
return view('admin.diritti-reali.show', [
'title' => 'Dettaglio Diritto Reale',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Diritti Reali' => route('admin.diritti-reali.index'),
'Dettaglio' => ''
]
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
return view('admin.diritti-reali.edit', [
'title' => 'Modifica Diritto Reale',
'breadcrumb' => [
'Dashboard' => route('admin.dashboard'),
'Diritti Reali' => route('admin.diritti-reali.index'),
'Modifica' => ''
]
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// TODO: Implement update logic
return redirect()->route('admin.diritti-reali.index')
->with('success', 'Diritto reale aggiornato con successo.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
// TODO: Implement destroy logic
return redirect()->route('admin.diritti-reali.index')
->with('success', 'Diritto reale eliminato con successo.');
}
}

View File

@ -0,0 +1,308 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\DocumentoStabile;
use App\Models\Stabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use ZipArchive;
class DocumentiController extends Controller
{
/**
* Carica nuovi documenti per uno stabile
*/
public function store(Request $request, Stabile $stabile)
{
$request->validate([
'documenti.*' => 'required|file|max:10240|mimes:pdf,doc,docx,xls,xlsx,jpg,jpeg,png,gif',
'categoria_documento' => 'required|string|in:' . implode(',', array_keys(DocumentoStabile::categorie()))
]);
$documentiCaricati = [];
$errori = [];
if ($request->hasFile('documenti')) {
foreach ($request->file('documenti') as $file) {
try {
// Genera nome unico per il file
$nomeOriginale = $file->getClientOriginalName();
$estensione = $file->getClientOriginalExtension();
$nomeFile = Str::slug(pathinfo($nomeOriginale, PATHINFO_FILENAME)) . '_' . time() . '.' . $estensione;
// Percorso di salvataggio: documenti/stabili/{stabile_id}/
$percorso = "documenti/stabili/{$stabile->id}";
$percorsoCompleto = $file->storeAs($percorso, $nomeFile, 'public');
// Crea record nel database
$documento = DocumentoStabile::create([
'stabile_id' => $stabile->id,
'nome_file' => $nomeFile,
'nome_originale' => $nomeOriginale,
'percorso_file' => $percorsoCompleto,
'categoria' => $request->categoria_documento,
'tipo_mime' => $file->getMimeType(),
'dimensione' => $file->getSize(),
'descrizione' => $request->descrizione_documento,
'data_scadenza' => $request->data_scadenza_documento,
'tags' => $request->tags_documento,
'pubblico' => $request->has('pubblico_documento'),
'caricato_da' => Auth::id()
]);
$documentiCaricati[] = $documento;
} catch (\Exception $e) {
$errori[] = "Errore nel caricamento di {$nomeOriginale}: " . $e->getMessage();
}
}
}
if (count($documentiCaricati) > 0) {
return response()->json([
'success' => true,
'message' => count($documentiCaricati) . ' documento/i caricato/i con successo',
'documenti' => $documentiCaricati,
'errori' => $errori
]);
} else {
return response()->json([
'success' => false,
'message' => 'Nessun documento caricato',
'errori' => $errori
], 400);
}
}
/**
* Lista documenti di uno stabile
*/
public function index(Stabile $stabile)
{
$documenti = $stabile->documenti()
->with('caricatore')
->orderBy('created_at', 'desc')
->get();
return response()->json([
'success' => true,
'documenti' => $documenti
]);
}
/**
* Download di un documento
*/
public function download(DocumentoStabile $documento)
{
if (!$documento->fileEsiste()) {
abort(404, 'File non trovato');
}
// Incrementa contatore download
$documento->incrementaDownload();
return Storage::disk('public')->download(
$documento->percorso_file,
$documento->nome_originale
);
}
/**
* Visualizza un documento nel browser
*/
public function view(DocumentoStabile $documento)
{
if (!$documento->fileEsiste()) {
abort(404, 'File non trovato');
}
// Incrementa contatore accessi
$documento->update(['ultimo_accesso' => now()]);
$path = Storage::disk('public')->path($documento->percorso_file);
return response()->file($path, [
'Content-Type' => $documento->tipo_mime,
'Content-Disposition' => 'inline; filename="' . $documento->nome_originale . '"'
]);
}
/**
* Elimina un documento
*/
public function destroy(DocumentoStabile $documento)
{
try {
$documento->delete(); // Il boot() del model si occuperà di eliminare il file fisico
return response()->json([
'success' => true,
'message' => 'Documento eliminato con successo'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Errore nell\'eliminazione del documento: ' . $e->getMessage()
], 500);
}
}
/**
* Download multiplo di documenti
*/
public function downloadMultiple(Request $request)
{
$request->validate([
'documento_ids' => 'required|array',
'documento_ids.*' => 'exists:documenti_stabili,id'
]);
$documenti = DocumentoStabile::whereIn('id', $request->documento_ids)->get();
if ($documenti->isEmpty()) {
return response()->json(['success' => false, 'message' => 'Nessun documento trovato'], 404);
}
// Crea un file ZIP temporaneo
$zipFileName = 'documenti_' . time() . '.zip';
$zipPath = storage_path('app/temp/' . $zipFileName);
// Assicurati che la directory temp esista
if (!file_exists(dirname($zipPath))) {
mkdir(dirname($zipPath), 0755, true);
}
$zip = new ZipArchive;
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
foreach ($documenti as $documento) {
if ($documento->fileEsiste()) {
$filePath = Storage::disk('public')->path($documento->percorso_file);
$zip->addFile($filePath, $documento->nome_originale);
$documento->incrementaDownload();
}
}
$zip->close();
return response()->download($zipPath, $zipFileName)->deleteFileAfterSend(true);
}
return response()->json(['success' => false, 'message' => 'Errore nella creazione dell\'archivio'], 500);
}
/**
* Eliminazione multipla di documenti
*/
public function deleteMultiple(Request $request)
{
$request->validate([
'documento_ids' => 'required|array',
'documento_ids.*' => 'exists:documenti_stabili,id'
]);
try {
$documenti = DocumentoStabile::whereIn('id', $request->documento_ids)->get();
$deletedCount = 0;
foreach ($documenti as $documento) {
$documento->delete();
$deletedCount++;
}
return response()->json([
'success' => true,
'message' => "{$deletedCount} documento/i eliminato/i con successo",
'deleted_count' => $deletedCount
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Errore nell\'eliminazione dei documenti: ' . $e->getMessage()
], 500);
}
}
/**
* Stampa elenco documenti
*/
public function printList(Stabile $stabile)
{
$documenti = $stabile->documenti()
->with('caricatore')
->orderBy('categoria')
->orderBy('created_at', 'desc')
->get()
->groupBy('categoria');
return view('admin.documenti.print-list', compact('stabile', 'documenti'));
}
/**
* Aggiorna i metadati di un documento
*/
public function updateMetadata(Request $request, DocumentoStabile $documento)
{
$request->validate([
'categoria' => 'required|string|in:' . implode(',', array_keys(DocumentoStabile::categorie())),
'descrizione' => 'nullable|string|max:1000',
'data_scadenza' => 'nullable|date',
'tags' => 'nullable|string',
'pubblico' => 'boolean'
]);
$documento->update($request->only([
'categoria', 'descrizione', 'data_scadenza', 'tags', 'pubblico'
]));
return response()->json([
'success' => true,
'message' => 'Metadati documento aggiornati con successo',
'documento' => $documento->fresh()
]);
}
/**
* Ricerca documenti
*/
public function search(Request $request, Stabile $stabile)
{
$query = $stabile->documenti();
if ($request->filled('categoria')) {
$query->where('categoria', $request->categoria);
}
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('nome_originale', 'like', "%{$search}%")
->orWhere('descrizione', 'like', "%{$search}%")
->orWhere('tags', 'like', "%{$search}%");
});
}
if ($request->filled('scadenza')) {
switch ($request->scadenza) {
case 'scaduti':
$query->scaduti();
break;
case 'in_scadenza':
$query->inScadenza(30);
break;
}
}
$documenti = $query->with('caricatore')
->orderBy('created_at', 'desc')
->get();
return response()->json([
'success' => true,
'documenti' => $documenti
]);
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Documento;
use App\Models\Stabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class DocumentoController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$query = Documento::with('documentable')
->whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
});
// Filtri
if ($request->filled('tipo_documento')) {
$query->where('tipo_documento', $request->tipo_documento);
}
if ($request->filled('search')) {
$query->where(function($q) use ($request) {
$q->where('nome_file', 'like', '%' . $request->search . '%')
->orWhere('descrizione', 'like', '%' . $request->search . '%');
});
}
$documenti = $query->orderBy('created_at', 'desc')->paginate(20);
// Tipi documento per filtro
$tipiDocumento = Documento::whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->distinct()->pluck('tipo_documento')->filter();
return view('admin.documenti.index', compact('documenti', 'tipiDocumento'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
$stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get();
return view('admin.documenti.create', compact('stabili'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$request->validate([
'documentable_type' => 'required|string',
'documentable_id' => 'required|integer',
'file' => 'required|file|max:10240', // 10MB max
'tipo_documento' => 'required|string|max:100',
'descrizione' => 'nullable|string',
]);
$file = $request->file('file');
$path = $file->store('documenti', 'public');
Documento::create([
'documentable_type' => $request->documentable_type,
'documentable_id' => $request->documentable_id,
'nome_file' => $file->getClientOriginalName(),
'path_file' => $path,
'tipo_documento' => $request->tipo_documento,
'descrizione' => $request->descrizione,
'mime_type' => $file->getMimeType(),
'dimensione_file' => $file->getSize(),
'hash_file' => hash_file('sha256', $file->path()),
]);
return redirect()->route('admin.documenti.index')
->with('success', 'Documento caricato con successo.');
}
/**
* Display the specified resource.
*/
public function show(Documento $documento)
{
// Verifica accesso
if ($documento->documentable_type === Stabile::class) {
$stabile = $documento->documentable;
if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
}
return view('admin.documenti.show', compact('documento'));
}
/**
* Download del documento
*/
public function download(Documento $documento)
{
// Verifica accesso
if ($documento->documentable_type === Stabile::class) {
$stabile = $documento->documentable;
if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
}
if (!Storage::disk('public')->exists($documento->path_file)) {
abort(404, 'File non trovato');
}
return Storage::disk('public')->download($documento->path_file, $documento->nome_file);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Documento $documento)
{
// Verifica accesso
if ($documento->documentable_type === Stabile::class) {
$stabile = $documento->documentable;
if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) {
abort(403);
}
}
// Elimina il file fisico
if (Storage::disk('public')->exists($documento->path_file)) {
Storage::disk('public')->delete($documento->path_file);
}
$documento->delete();
return redirect()->route('admin.documenti.index')
->with('success', 'Documento eliminato con successo.');
}
}

View File

@ -0,0 +1,242 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
class FileManagerController extends Controller
{
/**
* Mostra la gestione file dell'amministratore
*/
public function index()
{
$user = Auth::user();
// Verifica che l'utente sia un amministratore
if (!$user->hasRole('amministratore') || !$user->amministratore) {
abort(403, 'Accesso non autorizzato');
}
$amministratore = $user->amministratore;
$basePath = $amministratore->getFolderPath();
// Ottieni struttura cartelle
$folders = $this->getFolderStructure($basePath);
// Statistiche utilizzo spazio
$stats = $this->calculateStorageStats($basePath);
return view('admin.file-manager.index', compact('amministratore', 'folders', 'stats'));
}
/**
* Mostra contenuto di una cartella specifica
*/
public function folder(Request $request, $folder = '')
{
$user = Auth::user();
$amministratore = $user->amministratore;
$basePath = $amministratore->getFolderPath();
// Sanitizza il path per sicurezza
$safePath = $this->sanitizePath($folder);
$fullPath = $basePath . '/' . $safePath;
// Verifica che la cartella esista
if (!Storage::disk('local')->exists($fullPath)) {
abort(404, 'Cartella non trovata');
}
// Ottieni contenuto cartella
$files = Storage::disk('local')->files($fullPath);
$directories = Storage::disk('local')->directories($fullPath);
// Formatta per la vista
$formattedFiles = collect($files)->map(function ($file) {
return [
'name' => basename($file),
'path' => $file,
'size' => Storage::disk('local')->size($file),
'modified' => Storage::disk('local')->lastModified($file),
'type' => $this->getFileType($file),
];
});
$formattedDirs = collect($directories)->map(function ($dir) {
return [
'name' => basename($dir),
'path' => $dir,
'type' => 'folder',
];
});
return view('admin.file-manager.folder', compact(
'amministratore',
'formattedFiles',
'formattedDirs',
'safePath',
'fullPath'
));
}
/**
* Upload file nella cartella dell'amministratore
*/
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|max:10240', // Max 10MB
'folder' => 'nullable|string',
]);
$user = Auth::user();
$amministratore = $user->amministratore;
$basePath = $amministratore->getFolderPath();
$folder = $this->sanitizePath($request->folder ?? 'documenti/allegati');
$uploadPath = $basePath . '/' . $folder;
// Upload file
$file = $request->file('file');
$filename = time() . '_' . $file->getClientOriginalName();
$file->storeAs($uploadPath, $filename, 'local');
return redirect()->back()->with('success', "File {$filename} caricato con successo");
}
/**
* Download file dall'archivio amministratore
*/
public function download($filePath)
{
$user = Auth::user();
$amministratore = $user->amministratore;
$basePath = $amministratore->getFolderPath();
$safePath = $this->sanitizePath($filePath);
$fullPath = $basePath . '/' . $safePath;
// Verifica che il file esista e appartenga all'amministratore
if (!Storage::disk('local')->exists($fullPath)) {
abort(404, 'File non trovato');
}
return response()->download(storage_path("app/{$fullPath}"));
}
/**
* Ottieni struttura cartelle
*/
private function getFolderStructure($basePath): array
{
$structure = [
'documenti' => [
'allegati' => [],
'contratti' => [],
'assemblee' => [],
'preventivi' => [],
],
'backup' => [
'database' => [],
'files' => [],
],
'exports' => [],
'logs' => [],
];
foreach ($structure as $folder => $subfolders) {
if (is_array($subfolders)) {
foreach ($subfolders as $subfolder => $content) {
$path = "{$basePath}/{$folder}/{$subfolder}";
$structure[$folder][$subfolder] = $this->getFolderInfo($path);
}
} else {
$path = "{$basePath}/{$folder}";
$structure[$folder] = $this->getFolderInfo($path);
}
}
return $structure;
}
/**
* Ottieni info cartella
*/
private function getFolderInfo($path): array
{
if (!Storage::disk('local')->exists($path)) {
return ['files' => 0, 'size' => 0];
}
$files = Storage::disk('local')->allFiles($path);
$totalSize = 0;
foreach ($files as $file) {
$totalSize += Storage::disk('local')->size($file);
}
return [
'files' => count($files),
'size' => $totalSize,
];
}
/**
* Calcola statistiche storage
*/
private function calculateStorageStats($basePath): array
{
$allFiles = Storage::disk('local')->allFiles($basePath);
$totalSize = 0;
$fileTypes = [];
foreach ($allFiles as $file) {
$size = Storage::disk('local')->size($file);
$totalSize += $size;
$ext = pathinfo($file, PATHINFO_EXTENSION);
$fileTypes[$ext] = ($fileTypes[$ext] ?? 0) + 1;
}
return [
'total_files' => count($allFiles),
'total_size' => $totalSize,
'file_types' => $fileTypes,
];
}
/**
* Sanitizza path per sicurezza
*/
private function sanitizePath($path): string
{
// Rimuovi caratteri pericolosi
$path = str_replace(['../', '../', '..\\'], '', $path);
$path = trim($path, '/\\');
return $path;
}
/**
* Ottieni tipo file
*/
private function getFileType($file): string
{
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$types = [
'pdf' => 'document',
'doc' => 'document', 'docx' => 'document',
'xls' => 'spreadsheet', 'xlsx' => 'spreadsheet',
'jpg' => 'image', 'jpeg' => 'image', 'png' => 'image', 'gif' => 'image',
'zip' => 'archive', 'rar' => 'archive', '7z' => 'archive',
];
return $types[$ext] ?? 'file';
}
}

Some files were not shown because too many files have changed in this diff Show More