netgescon-master/docs/specifiche/MODULO_STABILI_AVANZATO.md
2025-07-20 14:57:25 +00:00

19 KiB

🏢 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)

-- 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

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

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

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

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

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

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

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

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

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)

<!-- 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

<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

@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! 🚀