📋 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
521 lines
19 KiB
Markdown
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!** 🚀
|