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

20 KiB

🏢 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

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

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

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)

@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

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