netgescon-master/docs/02-architettura-laravel/specifiche/MODULO_STABILI_AVANZATO.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

521 lines
19 KiB
Markdown

# 🏢 MODULO STABILI AVANZATO - Specifiche Implementazione
> **🎯 Primo modulo prioritario del piano implementazione**
> Basato su: brainstorming/01-stabili/ANALISI-STABILI.md
> Aggiornato: 14 Luglio 2025
## 📋 **OVERVIEW**
Evoluzione del CRUD Stabili esistente con funzionalità innovative:
-**Base CRUD** già implementato (StabileController, Model, Views)
- 🔄 **Struttura fisica automatica** (palazzine, scale, piani)
- 🔑 **Gestione chiavi con QR Code** (archivio, tipologie, tracking)
- 🏠 **Auto-generazione unità immobiliari** da struttura
- 💰 **Fondi condominiali gerarchici** (ordinario, riserva, specifici)
## 🏗️ **ARCHITETTURA DATABASE**
### **Tabelle Esistenti da Estendere**
#### **stabili** (ESTENSIONE)
```sql
-- Campi già esistenti: denominazione, codice_fiscale, indirizzo, etc.
-- NUOVI CAMPI DA AGGIUNGERE:
ALTER TABLE stabili ADD COLUMN struttura_fisica_json JSON;
ALTER TABLE stabili ADD COLUMN numero_palazzine INT DEFAULT 1;
ALTER TABLE stabili ADD COLUMN numero_scale_per_palazzina INT DEFAULT 1;
ALTER TABLE stabili ADD COLUMN numero_piani INT DEFAULT 3;
ALTER TABLE stabili ADD COLUMN piano_seminterrato BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN piano_sottotetto BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN presenza_ascensore BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN cortile_giardino BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN superficie_cortile DECIMAL(8,2) NULL;
ALTER TABLE stabili ADD COLUMN riscaldamento_centralizzato BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN acqua_centralizzata BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN gas_centralizzato BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN servizio_portineria BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN orari_portineria VARCHAR(255) NULL;
ALTER TABLE stabili ADD COLUMN videocitofono BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN antenna_tv_centralizzata BOOLEAN DEFAULT FALSE;
ALTER TABLE stabili ADD COLUMN internet_condominiale BOOLEAN DEFAULT FALSE;
```
### **Nuove Tabelle da Creare**
#### **1. chiavi_stabili**
```sql
CREATE TABLE chiavi_stabili (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
stabile_id BIGINT NOT NULL,
codice_chiave VARCHAR(50) UNIQUE NOT NULL,
qr_code_data TEXT NOT NULL,
tipologia ENUM('portone_principale', 'porte_secondarie', 'locali_tecnici',
'spazi_comuni', 'servizi', 'emergenza') NOT NULL,
descrizione VARCHAR(255) NOT NULL,
ubicazione VARCHAR(255),
numero_duplicati INT DEFAULT 1,
stato ENUM('attiva', 'smarrita', 'sostituita', 'fuori_uso') DEFAULT 'attiva',
assegnata_a VARCHAR(255) NULL, -- Chi ce l'ha attualmente
data_assegnazione TIMESTAMP NULL,
note TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (stabile_id) REFERENCES stabili(id_stabile) ON DELETE CASCADE,
INDEX idx_stabile_tipologia (stabile_id, tipologia),
INDEX idx_qr_code (qr_code_data(100))
);
```
#### **2. movimenti_chiavi**
```sql
CREATE TABLE movimenti_chiavi (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
chiave_id BIGINT NOT NULL,
tipo_movimento ENUM('assegnazione', 'riconsegna', 'smarrimento', 'sostituzione') NOT NULL,
data_movimento TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
assegnata_da VARCHAR(255), -- Chi ha fatto l'assegnazione
assegnata_a VARCHAR(255), -- A chi è stata assegnata
motivo VARCHAR(255),
note TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (chiave_id) REFERENCES chiavi_stabili(id) ON DELETE CASCADE,
INDEX idx_chiave_data (chiave_id, data_movimento)
);
```
#### **3. fondi_condominiali**
```sql
CREATE TABLE fondi_condominiali (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
stabile_id BIGINT NOT NULL,
tipo_fondo ENUM('ordinario', 'riserva', 'ascensore', 'riscaldamento',
'facciata_tetto', 'verde_giardini', 'sicurezza',
'innovazione', 'investimenti', 'personalizzato') NOT NULL,
denominazione VARCHAR(255) NOT NULL,
descrizione TEXT,
saldo_attuale DECIMAL(12,2) DEFAULT 0.00,
saldo_minimo DECIMAL(12,2) DEFAULT 0.00,
percentuale_accantonamento DECIMAL(5,2) DEFAULT 0.00,
attivo BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (stabile_id) REFERENCES stabili(id_stabile) ON DELETE CASCADE,
INDEX idx_stabile_tipo (stabile_id, tipo_fondo)
);
```
#### **4. struttura_fisica_dettaglio**
```sql
CREATE TABLE struttura_fisica_dettaglio (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
stabile_id BIGINT NOT NULL,
palazzina VARCHAR(10) NOT NULL, -- A, B, C o 1, 2, 3
scala VARCHAR(10), -- 1, 2, 3 o A, B, C
piano INT NOT NULL, -- -2, -1, 0, 1, 2, 3... (0=piano terra)
numero_interni INT DEFAULT 1,
presenza_ascensore BOOLEAN DEFAULT FALSE,
note TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (stabile_id) REFERENCES stabili(id_stabile) ON DELETE CASCADE,
INDEX idx_stabile_struttura (stabile_id, palazzina, scala, piano)
);
```
## 🎯 **FUNZIONALITÀ DA IMPLEMENTARE**
### **1. Gestione Struttura Fisica Automatica**
#### **StruttureFisicaController**
```php
class StruttureFisicaController extends Controller
{
public function generateUnitaImmobiliari(Request $request, Stabile $stabile)
{
// Calcola numero totale unità dalla struttura fisica
$struttura = $this->calcolaStrutturaFisica($stabile);
// Genera preview unità immobiliari
$preview = $this->generaPreviewUnita($struttura);
return view('admin.stabili.genera-unita', compact('stabile', 'preview'));
}
private function calcolaStrutturaFisica(Stabile $stabile)
{
$totalePalazzine = $stabile->numero_palazzine;
$scalePerPalazzina = $stabile->numero_scale_per_palazzina;
$numeroPiani = $stabile->numero_piani;
// ... logica calcolo
}
}
```
#### **Service: UnitaImmobiliareGenerator**
```php
class UnitaImmobiliareGenerator
{
public function generateFromStruttura(Stabile $stabile, array $config = [])
{
$unita = [];
for ($palazzina = 1; $palazzina <= $stabile->numero_palazzine; $palazzina++) {
for ($scala = 1; $scala <= $stabile->numero_scale_per_palazzina; $scala++) {
for ($piano = -1; $piano <= $stabile->numero_piani; $piano++) {
// Genera unità per questo piano
$unitaPiano = $this->generaUnitaPiano($stabile, $palazzina, $scala, $piano);
$unita = array_merge($unita, $unitaPiano);
}
}
}
return $unita;
}
}
```
### **2. Gestione Chiavi con QR Code**
#### **ChiaveController**
```php
class ChiaveController extends Controller
{
public function index(Stabile $stabile)
{
$chiavi = ChiaveStabile::where('stabile_id', $stabile->id_stabile)
->with('movimenti')
->paginate(20);
return view('admin.stabili.chiavi.index', compact('stabile', 'chiavi'));
}
public function create(Stabile $stabile)
{
$tipologie = ChiaveStabile::TIPOLOGIE;
return view('admin.stabili.chiavi.create', compact('stabile', 'tipologie'));
}
public function store(Request $request, Stabile $stabile)
{
$validated = $request->validate([
'tipologia' => 'required|in:portone_principale,porte_secondarie,locali_tecnici,spazi_comuni,servizi,emergenza',
'descrizione' => 'required|string|max:255',
'ubicazione' => 'nullable|string|max:255',
'numero_duplicati' => 'integer|min:1|max:50'
]);
$chiave = new ChiaveStabile();
$chiave->stabile_id = $stabile->id_stabile;
$chiave->codice_chiave = $this->generateCodiceChiave($stabile);
$chiave->qr_code_data = $this->generateQRCode($chiave);
$chiave->fill($validated);
$chiave->save();
return redirect()->route('admin.stabili.chiavi.index', $stabile)
->with('success', 'Chiave creata con successo');
}
private function generateQRCode(ChiaveStabile $chiave)
{
$data = [
'stabile_id' => $chiave->stabile_id,
'chiave_id' => $chiave->id,
'codice' => $chiave->codice_chiave,
'tipologia' => $chiave->tipologia,
'timestamp' => now()->timestamp
];
return json_encode($data);
}
}
```
#### **Model: ChiaveStabile**
```php
class ChiaveStabile extends Model
{
protected $table = 'chiavi_stabili';
const TIPOLOGIE = [
'portone_principale' => 'Portone Principale',
'porte_secondarie' => 'Porte Secondarie',
'locali_tecnici' => 'Locali Tecnici',
'spazi_comuni' => 'Spazi Comuni',
'servizi' => 'Servizi',
'emergenza' => 'Emergenza'
];
protected $fillable = [
'stabile_id', 'codice_chiave', 'qr_code_data', 'tipologia',
'descrizione', 'ubicazione', 'numero_duplicati', 'stato',
'assegnata_a', 'data_assegnazione', 'note'
];
public function stabile()
{
return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile');
}
public function movimenti()
{
return $this->hasMany(MovimentoChiave::class, 'chiave_id');
}
public function getQRCodeImageAttribute()
{
return QrCode::size(200)->generate($this->qr_code_data);
}
}
```
### **3. Fondi Condominiali Gerarchici**
#### **FondoCondominiale Model**
```php
class FondoCondominiale extends Model
{
protected $table = 'fondi_condominiali';
const TIPI_FONDO = [
'ordinario' => 'Fondo Ordinario',
'riserva' => 'Fondo di Riserva',
'ascensore' => 'Fondo Ascensore',
'riscaldamento' => 'Fondo Riscaldamento',
'facciata_tetto' => 'Fondo Facciata/Tetto',
'verde_giardini' => 'Fondo Verde/Giardini',
'sicurezza' => 'Fondo Sicurezza',
'innovazione' => 'Fondo Innovazione',
'investimenti' => 'Fondo Investimenti',
'personalizzato' => 'Fondo Personalizzato'
];
protected $fillable = [
'stabile_id', 'tipo_fondo', 'denominazione', 'descrizione',
'saldo_attuale', 'saldo_minimo', 'percentuale_accantonamento', 'attivo'
];
public function stabile()
{
return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile');
}
public function movimenti()
{
return $this->hasMany(MovimentoContabile::class, 'fondo_id');
}
}
```
## 🎨 **INTERFACCIA UTENTE**
### **Views da Creare/Modificare**
#### **admin/stabili/show.blade.php** (ESTENSIONE)
```html
<!-- Aggiungere nuovi tab -->
<div class="nav nav-pills" id="stabile-tabs">
<!-- Tab esistenti -->
<a class="nav-link" data-bs-toggle="pill" href="#struttura-fisica">Struttura Fisica</a>
<a class="nav-link" data-bs-toggle="pill" href="#chiavi">Gestione Chiavi</a>
<a class="nav-link" data-bs-toggle="pill" href="#fondi">Fondi Condominiali</a>
</div>
<div class="tab-content">
<!-- Tab Struttura Fisica -->
<div class="tab-pane" id="struttura-fisica">
@include('admin.stabili.partials.struttura-fisica')
</div>
<!-- Tab Chiavi -->
<div class="tab-pane" id="chiavi">
@include('admin.stabili.partials.chiavi')
</div>
<!-- Tab Fondi -->
<div class="tab-pane" id="fondi">
@include('admin.stabili.partials.fondi')
</div>
</div>
```
#### **admin/stabili/partials/struttura-fisica.blade.php**
```html
<div class="card">
<div class="card-header">
<h5>Struttura Fisica Stabile</h5>
<button class="btn btn-primary" onclick="generateUnita()">
<i class="fas fa-magic"></i> Genera Unità Immobiliari
</button>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<strong>Palazzine:</strong> {{ $stabile->numero_palazzine }}
</div>
<div class="col-md-3">
<strong>Scale per Palazzina:</strong> {{ $stabile->numero_scale_per_palazzina }}
</div>
<div class="col-md-3">
<strong>Piani:</strong> {{ $stabile->numero_piani }}
</div>
<div class="col-md-3">
<strong>Ascensore:</strong>
<span class="badge bg-{{ $stabile->presenza_ascensore ? 'success' : 'secondary' }}">
{{ $stabile->presenza_ascensore ? 'Sì' : 'No' }}
</span>
</div>
</div>
<!-- Visualizzazione grafica struttura -->
<div id="struttura-visual" class="mt-4">
@include('admin.stabili.partials.struttura-visual')
</div>
</div>
</div>
```
#### **admin/stabili/chiavi/index.blade.php**
```html
@extends('layouts.app-universal')
@section('content')
<div class="container">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h4>Gestione Chiavi - {{ $stabile->denominazione }}</h4>
<a href="{{ route('admin.stabili.chiavi.create', $stabile) }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Nuova Chiave
</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Codice</th>
<th>Tipologia</th>
<th>Descrizione</th>
<th>Stato</th>
<th>Assegnata a</th>
<th>QR Code</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@foreach($chiavi as $chiave)
<tr>
<td>{{ $chiave->codice_chiave }}</td>
<td>
<span class="badge bg-info">
{{ ChiaveStabile::TIPOLOGIE[$chiave->tipologia] }}
</span>
</td>
<td>{{ $chiave->descrizione }}</td>
<td>
<span class="badge bg-{{ $chiave->stato == 'attiva' ? 'success' : 'warning' }}">
{{ ucfirst($chiave->stato) }}
</span>
</td>
<td>{{ $chiave->assegnata_a ?? '-' }}</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="showQRCode('{{ $chiave->id }}')">
<i class="fas fa-qrcode"></i>
</button>
</td>
<td>
<div class="btn-group">
<a href="{{ route('admin.stabili.chiavi.show', [$stabile, $chiave]) }}" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i>
</a>
<a href="{{ route('admin.stabili.chiavi.edit', [$stabile, $chiave]) }}" class="btn btn-sm btn-warning">
<i class="fas fa-edit"></i>
</a>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $chiavi->links() }}
</div>
</div>
</div>
</div>
</div>
<!-- Modal QR Code -->
<div class="modal fade" id="qrCodeModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">QR Code Chiave</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div id="qrCodeContainer"></div>
</div>
</div>
</div>
</div>
@endsection
```
## 🚀 **IMPLEMENTAZIONE STEP-BY-STEP**
### **Step 1: Database** (1 giorno)
- [ ] Creare migration per estensione tabella `stabili`
- [ ] Creare migration per tabella `chiavi_stabili`
- [ ] Creare migration per tabella `movimenti_chiavi`
- [ ] Creare migration per tabella `fondi_condominiali`
- [ ] Creare migration per tabella `struttura_fisica_dettaglio`
### **Step 2: Models** (0.5 giorni)
- [ ] Estendere model `Stabile` con nuovi campi
- [ ] Creare model `ChiaveStabile`
- [ ] Creare model `MovimentoChiave`
- [ ] Creare model `FondoCondominiale`
- [ ] Creare model `StruttureFisicaDettaglio`
### **Step 3: Controllers** (2 giorni)
- [ ] Estendere `StabileController` con nuovi metodi
- [ ] Creare `ChiaveController` completo
- [ ] Creare `FondoCondominiale Controller`
- [ ] Creare `StruttureFisicaController`
### **Step 4: Views** (2 giorni)
- [ ] Estendere form create/edit stabili
- [ ] Creare views gestione chiavi
- [ ] Creare views gestione fondi
- [ ] Creare componenti visualizzazione struttura
### **Step 5: Features Avanzate** (2 giorni)
- [ ] Implementare generazione QR Code
- [ ] Sistema auto-generazione unità immobiliari
- [ ] Dashboard fondi con grafici
- [ ] API mobile per scanner QR
## 📊 **TESTING**
### **Unit Tests**
- [ ] Test generazione codici chiavi univoci
- [ ] Test calcolo struttura fisica
- [ ] Test auto-generazione unità
- [ ] Test gestione fondi
### **Integration Tests**
- [ ] Test workflow completo creazione stabile
- [ ] Test import/export configurazioni
- [ ] Test API QR Code scanner
---
**Questo modulo sarà la base per tutti gli altri. Una volta completato, avremo il pattern per implementare rapidamente gli altri moduli!** 🚀