📋 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
525 lines
20 KiB
Markdown
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*
|