netgescon-master/docs/moduli/01-MODULO-STABILE.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

🔄 Versione: 2025.07.19-1644
🎯 Sistema pronto per Git distribuito
2025-07-19 16:44:47 +02:00

525 lines
20 KiB
Markdown

# 🏢 MODULO STABILE - Specifiche Complete
## 📋 OVERVIEW
Il **Modulo Stabile** è il modulo fondamentale di NetGesCon che gestisce l'anagrafica completa degli stabili/condomini e rappresenta il punto di partenza per tutti gli altri moduli del sistema.
---
## 🎯 OBIETTIVI DEL MODULO
### ✅ Funzionalità Core
- **Anagrafica Stabile Completa**: Tutti i dati identificativi e tecnici
- **Codici Univoci**: Sistema di identificazione a 8 caratteri
- **Multi-Gestione**: Un amministratore può gestire più stabili
- **Configurazioni Personalizzate**: Settings specifici per tipologia stabile
- **Storico Modifiche**: Audit trail completo delle variazioni
### 🔗 Integrazione Sistema
- **Base per tutti i moduli**: Ogni funzionalità parte dal concetto di stabile
- **Permessi Granulari**: Controllo accessi per stabile
- **Dashboard Dedicata**: Vista riassuntiva per ogni stabile
- **API Complete**: Endpoint REST per integrazione
---
## 💾 STRUTTURA DATABASE
### 📊 Tabella: `stabili`
```sql
CREATE TABLE stabili (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
codice_stabile VARCHAR(8) UNIQUE NOT NULL COMMENT 'Codice univoco 8 caratteri',
denominazione VARCHAR(255) NOT NULL,
-- Dati Legali/Fiscali
codice_fiscale VARCHAR(16) UNIQUE,
partita_iva VARCHAR(11),
natura_giuridica ENUM('condominio', 'cooperativa', 'societa', 'altro') DEFAULT 'condominio',
-- Indirizzo Completo
indirizzo VARCHAR(255) NOT NULL,
numero_civico VARCHAR(10),
cap VARCHAR(5) NOT NULL,
citta VARCHAR(100) NOT NULL,
provincia CHAR(2) NOT NULL,
regione VARCHAR(50) NOT NULL,
nazione VARCHAR(50) DEFAULT 'Italia',
-- Dati Tecnici Edificio
anno_costruzione YEAR,
numero_piani TINYINT UNSIGNED,
numero_unita SMALLINT UNSIGNED NOT NULL DEFAULT 0,
superficie_totale DECIMAL(10,2),
tipologia_edificio ENUM('residenziale', 'commerciale', 'misto', 'industriale') DEFAULT 'residenziale',
-- Dati Catastali
foglio VARCHAR(10),
particella VARCHAR(10),
subalterno VARCHAR(10),
categoria_catastale VARCHAR(5),
classe_energetica ENUM('A4', 'A3', 'A2', 'A1', 'B', 'C', 'D', 'E', 'F', 'G'),
-- Gestione Amministrativa
user_id BIGINT UNSIGNED NOT NULL COMMENT 'Amministratore responsabile',
data_inizio_gestione DATE NOT NULL,
data_fine_gestione DATE NULL,
stato ENUM('attivo', 'sospeso', 'chiuso') DEFAULT 'attivo',
-- Configurazioni
configurazioni JSON COMMENT 'Settings specifici stabile',
-- Timestamps e Audit
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT UNSIGNED,
updated_by BIGINT UNSIGNED,
-- Indici
INDEX idx_codice_stabile (codice_stabile),
INDEX idx_user_id (user_id),
INDEX idx_stato (stato),
INDEX idx_citta_provincia (citta, provincia),
-- Foreign Keys
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 📊 Tabella: `stabili_configurazioni`
```sql
CREATE TABLE stabili_configurazioni (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
-- Configurazioni Contabili
piano_conti_personalizzato BOOLEAN DEFAULT FALSE,
gestione_anticipi BOOLEAN DEFAULT TRUE,
calcolo_interessi_mora BOOLEAN DEFAULT TRUE,
tasso_interesse_mora DECIMAL(5,2) DEFAULT 0.50,
-- Configurazioni Millesimali
numero_tabelle_millesimali TINYINT DEFAULT 1,
aggiornamento_automatico_millesimi BOOLEAN DEFAULT FALSE,
validazione_matematica_millesimi BOOLEAN DEFAULT TRUE,
-- Configurazioni Comunicazioni
email_amministratore VARCHAR(255),
telefono_amministratore VARCHAR(20),
pec_stabile VARCHAR(255),
sito_web VARCHAR(255),
-- Configurazioni Fatturazione
formato_fattura ENUM('standard', 'semplificata', 'elettronica') DEFAULT 'standard',
numerazione_fatture VARCHAR(50) DEFAULT 'YYYY/NNNN',
iva_default DECIMAL(5,2) DEFAULT 22.00,
-- Configurazioni Backup
backup_automatico BOOLEAN DEFAULT TRUE,
frequenza_backup ENUM('giornaliero', 'settimanale', 'mensile') DEFAULT 'settimanale',
retention_backup SMALLINT DEFAULT 12 COMMENT 'Mesi',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
UNIQUE KEY uk_stabile_configurazioni (stabile_id)
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
---
## 🔧 CONTROLLER LARAVEL
### 📝 StabileController
```php
<?php
namespace App\Http\Controllers;
use App\Models\Stabile;
use App\Http\Requests\StabileRequest;
use App\Services\StabileService;
use Illuminate\Http\Request;
class StabileController extends Controller
{
protected $stabileService;
public function __construct(StabileService $stabileService)
{
$this->stabileService = $stabileService;
$this->middleware('auth');
$this->middleware('permission:stabili.view')->only(['index', 'show']);
$this->middleware('permission:stabili.create')->only(['create', 'store']);
$this->middleware('permission:stabili.edit')->only(['edit', 'update']);
$this->middleware('permission:stabili.delete')->only(['destroy']);
}
/**
* Lista stabili con filtri e ricerca
*/
public function index(Request $request)
{
$stabili = $this->stabileService->getFilteredStabili($request->all());
return view('stabili.index', compact('stabili'));
}
/**
* Form creazione nuovo stabile
*/
public function create()
{
return view('stabili.create');
}
/**
* Salvataggio nuovo stabile
*/
public function store(StabileRequest $request)
{
try {
$stabile = $this->stabileService->createStabile($request->validated());
return redirect()->route('stabili.show', $stabile)
->with('success', 'Stabile creato con successo!');
} catch (\Exception $e) {
return back()->withInput()
->with('error', 'Errore durante la creazione: ' . $e->getMessage());
}
}
/**
* Visualizzazione dettaglio stabile
*/
public function show(Stabile $stabile)
{
$this->authorize('view', $stabile);
$stabile->load(['unita', 'configurazioni', 'documenti']);
$statistiche = $this->stabileService->getStatistiche($stabile);
return view('stabili.show', compact('stabile', 'statistiche'));
}
/**
* Form modifica stabile
*/
public function edit(Stabile $stabile)
{
$this->authorize('update', $stabile);
return view('stabili.edit', compact('stabile'));
}
/**
* Aggiornamento stabile
*/
public function update(StabileRequest $request, Stabile $stabile)
{
$this->authorize('update', $stabile);
try {
$this->stabileService->updateStabile($stabile, $request->validated());
return redirect()->route('stabili.show', $stabile)
->with('success', 'Stabile aggiornato con successo!');
} catch (\Exception $e) {
return back()->withInput()
->with('error', 'Errore durante l\'aggiornamento: ' . $e->getMessage());
}
}
/**
* Eliminazione stabile (soft delete)
*/
public function destroy(Stabile $stabile)
{
$this->authorize('delete', $stabile);
try {
$this->stabileService->deleteStabile($stabile);
return redirect()->route('stabili.index')
->with('success', 'Stabile eliminato con successo!');
} catch (\Exception $e) {
return back()->with('error', 'Errore durante l\'eliminazione: ' . $e->getMessage());
}
}
/**
* Dashboard stabile con statistiche
*/
public function dashboard(Stabile $stabile)
{
$this->authorize('view', $stabile);
$dashboard = $this->stabileService->getDashboardData($stabile);
return view('stabili.dashboard', compact('stabile', 'dashboard'));
}
}
```
---
## 🎨 INTERFACCE UTENTE
### 📋 Lista Stabili (`stabili/index.blade.php`)
```html
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h3>🏢 Gestione Stabili</h3>
@can('stabili.create')
<a href="{{ route('stabili.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Nuovo Stabile
</a>
@endcan
</div>
<div class="card-body">
<!-- Filtri di Ricerca -->
<form method="GET" class="mb-4">
<div class="row">
<div class="col-md-3">
<input type="text" name="search" class="form-control"
placeholder="Cerca per denominazione..."
value="{{ request('search') }}">
</div>
<div class="col-md-2">
<select name="stato" class="form-control">
<option value="">Tutti gli stati</option>
<option value="attivo" {{ request('stato') == 'attivo' ? 'selected' : '' }}>Attivo</option>
<option value="sospeso" {{ request('stato') == 'sospeso' ? 'selected' : '' }}>Sospeso</option>
<option value="chiuso" {{ request('stato') == 'chiuso' ? 'selected' : '' }}>Chiuso</option>
</select>
</div>
<div class="col-md-2">
<select name="provincia" class="form-control">
<option value="">Tutte le province</option>
@foreach($province as $prov)
<option value="{{ $prov }}" {{ request('provincia') == $prov ? 'selected' : '' }}>
{{ $prov }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-primary">
<i class="fas fa-search"></i> Cerca
</button>
</div>
</div>
</form>
<!-- Tabella Stabili -->
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Codice</th>
<th>Denominazione</th>
<th>Indirizzo</th>
<th>Unità</th>
<th>Stato</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@forelse($stabili as $stabile)
<tr>
<td>
<span class="badge badge-secondary">{{ $stabile->codice_stabile }}</span>
</td>
<td>
<strong>{{ $stabile->denominazione }}</strong>
@if($stabile->tipologia_edificio)
<br><small class="text-muted">{{ ucfirst($stabile->tipologia_edificio) }}</small>
@endif
</td>
<td>
{{ $stabile->indirizzo }} {{ $stabile->numero_civico }}<br>
<small class="text-muted">{{ $stabile->cap }} {{ $stabile->citta }} ({{ $stabile->provincia }})</small>
</td>
<td>
<span class="badge badge-info">{{ $stabile->numero_unita }} unità</span>
</td>
<td>
@switch($stabile->stato)
@case('attivo')
<span class="badge badge-success">Attivo</span>
@break
@case('sospeso')
<span class="badge badge-warning">Sospeso</span>
@break
@case('chiuso')
<span class="badge badge-danger">Chiuso</span>
@break
@endswitch
</td>
<td>
<div class="btn-group" role="group">
@can('view', $stabile)
<a href="{{ route('stabili.show', $stabile) }}"
class="btn btn-sm btn-info" title="Visualizza">
<i class="fas fa-eye"></i>
</a>
@endcan
@can('update', $stabile)
<a href="{{ route('stabili.edit', $stabile) }}"
class="btn btn-sm btn-warning" title="Modifica">
<i class="fas fa-edit"></i>
</a>
@endcan
@can('delete', $stabile)
<form method="POST" action="{{ route('stabili.destroy', $stabile) }}"
class="d-inline" onsubmit="return confirm('Sei sicuro?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger" title="Elimina">
<i class="fas fa-trash"></i>
</button>
</form>
@endcan
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center">
<em>Nessuno stabile trovato</em>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
{{ $stabili->links() }}
</div>
</div>
</div>
</div>
</div>
@endsection
```
---
## 🔄 WORKFLOW OPERATIVO
### 📝 Processo Creazione Stabile
1. **Accesso Form**: Admin accede al form di creazione
2. **Validazione Input**: Controllo dati obbligatori e formati
3. **Generazione Codice**: Sistema genera codice univoco 8 caratteri
4. **Salvataggio Database**: Insert in tabella `stabili` con audit
5. **Configurazioni Default**: Creazione configurazioni iniziali
6. **Notifica**: Conferma successo e redirect a dettaglio
### 📊 Dashboard Stabile
1. **Dati Anagrafici**: Visualizzazione completa info stabile
2. **Statistiche**: Numero unità, superfici, millesimi totali
3. **Azioni Rapide**: Link diretti a funzioni principali
4. **Stato Sistema**: Indicatori salute dati e configurazioni
5. **Attività Recenti**: Log ultime modifiche e operazioni
---
## 🔒 SICUREZZA E PERMESSI
### 👥 Livelli Accesso
- **Super Admin**: Accesso totale a tutti gli stabili
- **Amministratore**: Accesso solo ai propri stabili
- **Collaboratore**: Accesso limitato in base alle assegnazioni
- **Consulente**: Solo visualizzazione dati autorizzati
### 🛡️ Policy Laravel
```php
class StabilePolicy
{
public function view(User $user, Stabile $stabile)
{
return $user->hasPermissionTo('stabili.view') &&
($user->hasRole('super-admin') || $user->id === $stabile->user_id);
}
public function create(User $user)
{
return $user->hasPermissionTo('stabili.create');
}
public function update(User $user, Stabile $stabile)
{
return $user->hasPermissionTo('stabili.edit') &&
($user->hasRole('super-admin') || $user->id === $stabile->user_id);
}
public function delete(User $user, Stabile $stabile)
{
return $user->hasPermissionTo('stabili.delete') &&
$user->hasRole('super-admin');
}
}
```
---
## ✅ TESTING E VALIDAZIONE
### 🧪 Test Unitari
- Validazione creazione stabile
- Verifica unicità codice stabile
- Test configurazioni default
- Controllo permessi accesso
### 🔍 Test Integrazione
- Workflow completo CRUD
- Collegamento con altri moduli
- Test performance query
- Validazione audit trail
---
## 📈 METRICHE E KPI
### 📊 Indicatori Performance
- Tempo risposta liste stabili (< 200ms)
- Accuratezza dati anagrafici (100%)
- Percentuale uptime sistema (99.9%)
- Soddisfazione utenti (> 4.5/5)
### 📋 Reportistica
- Report mensile stabili gestiti
- Statistiche utilizzo funzionalità
- Analisi errori e problematiche
- Trend crescita database
---
## 🚀 PROSSIMI SVILUPPI
### 🔄 Fase 2 - Miglioramenti
- Import massivo da file Excel/CSV
- API REST complete per integrazioni
- Backup automatico differenziale
- Notificazioni push importanti
### 🎯 Fase 3 - Avanzate
- Dashboard analytics avanzate
- Machine learning per previsioni
- Integrazione con servizi esterni
- Mobile app dedicata
---
*Documento tecnico Modulo Stabile | Versione 1.0 | Luglio 2025*