netgescon-day0/docs/91-database/02-tabelle-collegate/gestioni.md

1004 lines
36 KiB
Markdown

# 📅 TABELLA GESTIONI
## Periodi amministrativi annuali per ogni stabile
---
## 🎯 **CARATTERISTICHE TABELLA**
### **Scopo**:
Tabella per gestire i periodi amministrativi di ogni stabile - tipicamente annuali (1 Gen - 31 Dic)
### **Principio**:
**Collegamento via stabile** - ogni gestione appartiene a uno stabile → isolamento automatico per amministratore
---
## 🗄️ **STRUTTURA TABELLA**
```sql
CREATE TABLE gestioni (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL, -- FK verso stabili
-- Periodo gestionale
anno_gestione YEAR NOT NULL, -- Anno di riferimento (2024, 2025, ecc)
data_inizio DATE NOT NULL, -- Tipicamente 01/01/YYYY
data_fine DATE NOT NULL, -- Tipicamente 31/12/YYYY
denominazione VARCHAR(255) NULL, -- "Gestione 2024" o custom
-- Stato gestione
stato ENUM('pianificata', 'attiva', 'chiusa', 'archiviata') DEFAULT 'pianificata',
is_active BOOLEAN DEFAULT TRUE,
data_approvazione_bilancio DATE NULL, -- Quando l'assemblea approva
data_chiusura DATE NULL, -- Quando si chiude contabilmente
-- Budget e limiti
budget_preventivo DECIMAL(12, 2) DEFAULT 0.00, -- Budget previsto
budget_consuntivo DECIMAL(12, 2) DEFAULT 0.00, -- Budget effettivo
limite_spese_straordinarie DECIMAL(10, 2) NULL, -- Limite per spese senza assemblea
-- Configurazioni gestione
usa_fondo_di_riserva BOOLEAN DEFAULT TRUE,
percentuale_fondo_riserva DECIMAL(5, 2) DEFAULT 10.00, -- % da accantonare
rata_ordinaria_mensile DECIMAL(8, 2) DEFAULT 0.00, -- Rata mensile media
-- Dati assembleare
data_ultima_assemblea DATE NULL,
data_prossima_assemblea DATE NULL,
verbale_assemblea_path VARCHAR(500) NULL, -- Path al verbale
-- Millesimi e ripartizioni
usa_millesimi_generali BOOLEAN DEFAULT TRUE,
usa_millesimi_riscaldamento BOOLEAN DEFAULT FALSE,
usa_millesimi_ascensore BOOLEAN DEFAULT FALSE,
configurazione_millesimi JSON NULL, -- Config personalizzate
-- Note e configurazioni
note_gestione TEXT NULL,
configurazioni JSON NULL, -- Config specifiche gestione
-- Audit
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Foreign Keys
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
-- Indici
INDEX idx_stabile_id (stabile_id),
INDEX idx_anno_gestione (anno_gestione),
INDEX idx_stato (stato),
INDEX idx_active (is_active),
INDEX idx_data_inizio (data_inizio),
INDEX idx_data_fine (data_fine),
-- Indici composti
INDEX idx_stabile_anno (stabile_id, anno_gestione),
INDEX idx_stabile_active (stabile_id, is_active),
-- Vincolo unicità: un solo anno per stabile
UNIQUE KEY unique_stabile_anno (stabile_id, anno_gestione)
);
```
---
## 🏗️ **MIGRATION LARAVEL**
### **File**: `database/migrations/2024_08_05_000020_create_gestioni_table.php`
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('gestioni', function (Blueprint $table) {
$table->id();
$table->foreignId('stabile_id')
->constrained('stabili')
->onDelete('cascade');
// Periodo gestionale
$table->year('anno_gestione');
$table->date('data_inizio');
$table->date('data_fine');
$table->string('denominazione')->nullable();
// Stato gestione
$table->enum('stato', ['pianificata', 'attiva', 'chiusa', 'archiviata'])
->default('pianificata');
$table->boolean('is_active')->default(true);
$table->date('data_approvazione_bilancio')->nullable();
$table->date('data_chiusura')->nullable();
// Budget e limiti
$table->decimal('budget_preventivo', 12, 2)->default(0.00);
$table->decimal('budget_consuntivo', 12, 2)->default(0.00);
$table->decimal('limite_spese_straordinarie', 10, 2)->nullable();
// Configurazioni gestione
$table->boolean('usa_fondo_di_riserva')->default(true);
$table->decimal('percentuale_fondo_riserva', 5, 2)->default(10.00);
$table->decimal('rata_ordinaria_mensile', 8, 2)->default(0.00);
// Dati assembleare
$table->date('data_ultima_assemblea')->nullable();
$table->date('data_prossima_assemblea')->nullable();
$table->string('verbale_assemblea_path', 500)->nullable();
// Millesimi e ripartizioni
$table->boolean('usa_millesimi_generali')->default(true);
$table->boolean('usa_millesimi_riscaldamento')->default(false);
$table->boolean('usa_millesimi_ascensore')->default(false);
$table->json('configurazione_millesimi')->nullable();
// Note e configurazioni
$table->text('note_gestione')->nullable();
$table->json('configurazioni')->nullable();
$table->timestamps();
// Indici
$table->index(['stabile_id']);
$table->index(['anno_gestione']);
$table->index(['stato']);
$table->index(['is_active']);
$table->index(['data_inizio']);
$table->index(['data_fine']);
// Indici composti
$table->index(['stabile_id', 'anno_gestione'], 'idx_stabile_anno');
$table->index(['stabile_id', 'is_active'], 'idx_stabile_active');
// Vincolo unicità
$table->unique(['stabile_id', 'anno_gestione'], 'unique_stabile_anno');
});
}
public function down(): void
{
Schema::dropIfExists('gestioni');
}
};
```
---
## 📊 **MODEL ELOQUENT**
### **File**: `app/Models/Gestione.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Carbon\Carbon;
class Gestione extends Model
{
protected $fillable = [
'stabile_id',
'anno_gestione',
'data_inizio',
'data_fine',
'denominazione',
'stato',
'is_active',
'data_approvazione_bilancio',
'data_chiusura',
'budget_preventivo',
'budget_consuntivo',
'limite_spese_straordinarie',
'usa_fondo_di_riserva',
'percentuale_fondo_riserva',
'rata_ordinaria_mensile',
'data_ultima_assemblea',
'data_prossima_assemblea',
'verbale_assemblea_path',
'usa_millesimi_generali',
'usa_millesimi_riscaldamento',
'usa_millesimi_ascensore',
'configurazione_millesimi',
'note_gestione',
'configurazioni',
];
protected $casts = [
'data_inizio' => 'date',
'data_fine' => 'date',
'data_approvazione_bilancio' => 'date',
'data_chiusura' => 'date',
'data_ultima_assemblea' => 'date',
'data_prossima_assemblea' => 'date',
'is_active' => 'boolean',
'usa_fondo_di_riserva' => 'boolean',
'usa_millesimi_generali' => 'boolean',
'usa_millesimi_riscaldamento' => 'boolean',
'usa_millesimi_ascensore' => 'boolean',
'budget_preventivo' => 'decimal:2',
'budget_consuntivo' => 'decimal:2',
'limite_spese_straordinarie' => 'decimal:2',
'percentuale_fondo_riserva' => 'decimal:2',
'rata_ordinaria_mensile' => 'decimal:2',
'anno_gestione' => 'integer',
'configurazione_millesimi' => 'array',
'configurazioni' => 'array',
];
// Relazioni
public function stabile(): BelongsTo
{
return $this->belongsTo(Stabile::class);
}
public function contabilitaRegistrazioni(): HasMany
{
return $this->hasMany(ContabilitaRegistrazione::class);
}
public function movimentiContabili(): HasMany
{
return $this->hasMany(MovimentoContabile::class);
}
public function bilanci(): HasMany
{
return $this->hasMany(Bilancio::class);
}
// Scope automatico per amministratore tramite stabile
protected static function booted(): void
{
static::addGlobalScope('amministratore', function (Builder $builder) {
$user = auth()->user();
if ($user && $user->amministratore_id) {
$builder->whereHas('stabile', function ($query) use ($user) {
$query->where('amministratore_id', $user->amministratore_id);
});
}
});
}
// Scope
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeByStato(Builder $query, string $stato): Builder
{
return $query->where('stato', $stato);
}
public function scopeByAnno(Builder $query, int $anno): Builder
{
return $query->where('anno_gestione', $anno);
}
public function scopeInCorso(Builder $query): Builder
{
$oggi = now()->toDateString();
return $query->where('data_inizio', '<=', $oggi)
->where('data_fine', '>=', $oggi);
}
public function scopeScadute(Builder $query): Builder
{
return $query->where('data_fine', '<', now()->toDateString())
->where('stato', '!=', 'chiusa');
}
public function scopeConAssembleaProssima(Builder $query, int $giorni = 30): Builder
{
return $query->whereNotNull('data_prossima_assemblea')
->whereBetween('data_prossima_assemblea', [
now()->toDateString(),
now()->addDays($giorni)->toDateString()
]);
}
// Accessors
public function getDenominazioneComputedAttribute(): string
{
return $this->denominazione ?: "Gestione {$this->anno_gestione}";
}
public function getIsInCorsoAttribute(): bool
{
$oggi = now()->toDate();
return $this->data_inizio <= $oggi && $this->data_fine >= $oggi;
}
public function getIsScadutaAttribute(): bool
{
return $this->data_fine < now()->toDate() && $this->stato !== 'chiusa';
}
public function getGiorniRimanentiAttribute(): int
{
if ($this->is_scaduta) {
return 0;
}
return now()->diffInDays($this->data_fine, false);
}
public function getPercentualeCompletatoAttribute(): float
{
$totaleGiorni = $this->data_inizio->diffInDays($this->data_fine);
$giorniTrascorsi = $this->data_inizio->diffInDays(now());
if ($totaleGiorni <= 0) return 100.0;
$percentuale = ($giorniTrascorsi / $totaleGiorni) * 100;
return min(100.0, max(0.0, $percentuale));
}
public function getBudgetUtilizzatoAttribute(): float
{
if ($this->budget_preventivo <= 0) return 0.0;
return ($this->budget_consuntivo / $this->budget_preventivo) * 100;
}
public function getStatoColorAttribute(): string
{
return match($this->stato) {
'pianificata' => 'blue',
'attiva' => 'green',
'chiusa' => 'gray',
'archiviata' => 'purple',
default => 'gray'
};
}
// Helper Methods
public function calcolaBudgetConsuntivo(): void
{
$totaleSpese = $this->contabilitaRegistrazioni()
->where('tipo_movimento', 'uscita')
->sum('importo');
$this->update(['budget_consuntivo' => $totaleSpese]);
}
public function calcolaRataOrdinariaMensile(): void
{
$unitaCount = $this->stabile->numero_unita;
if ($unitaCount > 0 && $this->budget_preventivo > 0) {
$rataMensile = $this->budget_preventivo / 12 / $unitaCount;
$this->update(['rata_ordinaria_mensile' => $rataMensile]);
}
}
public function iniziaGestione(): bool
{
if ($this->stato !== 'pianificata') {
return false;
}
// Chiudi eventuali gestioni attive per lo stesso stabile
$this->stabile->gestioni()
->where('id', '!=', $this->id)
->where('stato', 'attiva')
->update(['stato' => 'chiusa', 'is_active' => false]);
$this->update([
'stato' => 'attiva',
'is_active' => true,
'data_inizio' => $this->data_inizio ?: now()->startOfYear(),
]);
return true;
}
public function chiudiGestione(): bool
{
if (!in_array($this->stato, ['attiva', 'pianificata'])) {
return false;
}
$this->calcolaBudgetConsuntivo();
$this->update([
'stato' => 'chiusa',
'data_chiusura' => now()->toDateString(),
'is_active' => false,
]);
return true;
}
public function archiviaGestione(): bool
{
if ($this->stato !== 'chiusa') {
return false;
}
$this->update(['stato' => 'archiviata']);
return true;
}
public function generaBilancio(): array
{
$entrate = $this->contabilitaRegistrazioni()
->where('tipo_movimento', 'entrata')
->sum('importo');
$uscite = $this->contabilitaRegistrazioni()
->where('tipo_movimento', 'uscita')
->sum('importo');
$saldo = $entrate - $uscite;
$fondoRiserva = $this->usa_fondo_di_riserva
? ($entrate * $this->percentuale_fondo_riserva / 100)
: 0;
return [
'entrate_totali' => $entrate,
'uscite_totali' => $uscite,
'saldo' => $saldo,
'fondo_riserva' => $fondoRiserva,
'saldo_disponibile' => $saldo - $fondoRiserva,
'budget_preventivo' => $this->budget_preventivo,
'scostamento_budget' => $uscite - $this->budget_preventivo,
'percentuale_utilizzo_budget' => $this->budget_utilizzato,
];
}
public function canDelete(): bool
{
return $this->contabilitaRegistrazioni()->count() === 0;
}
// Observer Events
protected static function boot(): void
{
parent::boot();
static::creating(function ($gestione) {
// Genera denominazione automatica se non specificata
if (!$gestione->denominazione) {
$gestione->denominazione = "Gestione {$gestione->anno_gestione}";
}
// Imposta date default se non specificate
if (!$gestione->data_inizio) {
$gestione->data_inizio = Carbon::createFromDate($gestione->anno_gestione, 1, 1);
}
if (!$gestione->data_fine) {
$gestione->data_fine = Carbon::createFromDate($gestione->anno_gestione, 12, 31);
}
});
static::saved(function ($gestione) {
// Ricalcola rata mensile quando cambia il budget
if ($gestione->wasChanged('budget_preventivo')) {
$gestione->calcolaRataOrdinariaMensile();
}
});
}
}
```
---
## 🌱 **SEEDER DATI DEMO**
### **File**: `database/seeders/GestioniSeeder.php`
```php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Gestione;
use App\Models\Stabile;
use Carbon\Carbon;
class GestioniSeeder extends Seeder
{
public function run(): void
{
$stabili = Stabile::all();
foreach ($stabili as $stabile) {
// Gestione 2023 - Chiusa
Gestione::create([
'stabile_id' => $stabile->id,
'anno_gestione' => 2023,
'data_inizio' => '2023-01-01',
'data_fine' => '2023-12-31',
'denominazione' => 'Gestione 2023',
'stato' => 'chiusa',
'is_active' => false,
'data_approvazione_bilancio' => '2024-03-15',
'data_chiusura' => '2024-01-31',
'budget_preventivo' => 50000.00,
'budget_consuntivo' => 48750.50,
'limite_spese_straordinarie' => 5000.00,
'usa_fondo_di_riserva' => true,
'percentuale_fondo_riserva' => 10.00,
'rata_ordinaria_mensile' => 200.00,
'data_ultima_assemblea' => '2024-03-15',
'usa_millesimi_generali' => true,
'usa_millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato,
'usa_millesimi_ascensore' => $stabile->ascensore,
'note_gestione' => 'Gestione conclusa regolarmente. Avanzo di cassa €1.249,50',
]);
// Gestione 2024 - Attiva
Gestione::create([
'stabile_id' => $stabile->id,
'anno_gestione' => 2024,
'data_inizio' => '2024-01-01',
'data_fine' => '2024-12-31',
'denominazione' => 'Gestione 2024',
'stato' => 'attiva',
'is_active' => true,
'data_approvazione_bilancio' => '2024-03-15',
'budget_preventivo' => 55000.00,
'budget_consuntivo' => 32450.75, // Parziale (agosto)
'limite_spese_straordinarie' => 6000.00,
'usa_fondo_di_riserva' => true,
'percentuale_fondo_riserva' => 12.00,
'rata_ordinaria_mensile' => 220.00,
'data_ultima_assemblea' => '2024-03-15',
'data_prossima_assemblea' => '2025-03-15',
'usa_millesimi_generali' => true,
'usa_millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato,
'usa_millesimi_ascensore' => $stabile->ascensore,
'configurazione_millesimi' => [
'millesimi_generali' => true,
'millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato,
'millesimi_ascensore' => $stabile->ascensore,
'calcolo_automatico' => true,
],
'note_gestione' => 'Gestione in corso. Previsione chiusura in pareggio.',
]);
// Gestione 2025 - Pianificata (solo per stabili professional/enterprise)
if ($stabile->amministratore->piano_abbonamento !== 'base') {
Gestione::create([
'stabile_id' => $stabile->id,
'anno_gestione' => 2025,
'data_inizio' => '2025-01-01',
'data_fine' => '2025-12-31',
'denominazione' => 'Gestione 2025',
'stato' => 'pianificata',
'is_active' => false,
'budget_preventivo' => 58000.00,
'limite_spese_straordinarie' => 7000.00,
'usa_fondo_di_riserva' => true,
'percentuale_fondo_riserva' => 15.00,
'rata_ordinaria_mensile' => 240.00,
'data_prossima_assemblea' => '2025-03-15',
'usa_millesimi_generali' => true,
'usa_millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato,
'usa_millesimi_ascensore' => $stabile->ascensore,
'note_gestione' => 'Gestione in fase di pianificazione. Budget incrementato del 5%.',
]);
}
}
$totalGestioni = Gestione::count();
$this->command->info("✅ Create {$totalGestioni} gestioni per tutti gli stabili");
// Mostra riassunto per amministratore
$stabili->groupBy('amministratore_id')->each(function ($stabiliGroup) {
$admin = $stabiliGroup->first()->amministratore;
$gestioniCount = Gestione::whereIn('stabile_id', $stabiliGroup->pluck('id'))->count();
$this->command->info("Admin {$admin->codice_univoco}: {$gestioniCount} gestioni");
});
}
}
```
---
## 🔍 **QUERY PATTERNS**
### **Dashboard Gestioni**:
```php
// Controller - Overview gestioni per amministratore
public function dashboard()
{
$amministratore = auth()->user()->amministratore;
$stats = [
'gestioni_attive' => Gestione::byStato('attiva')->count(),
'gestioni_in_corso' => Gestione::inCorso()->count(),
'gestioni_scadute' => Gestione::scadute()->count(),
'assemblee_prossime' => Gestione::conAssembleaProssima()->count(),
// Budget aggregato
'budget_totale_preventivo' => Gestione::byStato('attiva')->sum('budget_preventivo'),
'budget_totale_consuntivo' => Gestione::byStato('attiva')->sum('budget_consuntivo'),
// Rate mensili
'rate_mensili_totali' => Gestione::byStato('attiva')->sum('rata_ordinaria_mensile'),
];
// Gestioni con scadenze
$gestioniScadenze = Gestione::with('stabile')
->where(function($query) {
$query->conAssembleaProssima(30)
->orWhere('scadute');
})
->orderBy('data_prossima_assemblea')
->limit(10)
->get();
return view('gestioni.dashboard', compact('stats', 'gestioniScadenze'));
}
```
### **Lista Gestioni per Stabile**:
```php
// Controller - Gestioni di uno stabile specifico
public function indexByStabile(Stabile $stabile)
{
$gestioni = $stabile->gestioni()
->orderByDesc('anno_gestione')
->with(['contabilitaRegistrazioni'])
->get()
->map(function($gestione) {
$bilancio = $gestione->generaBilancio();
$gestione->bilancio_computed = $bilancio;
return $gestione;
});
return view('gestioni.index', compact('stabile', 'gestioni'));
}
```
### **Ricerca Avanzata**:
```php
// Controller - Ricerca gestioni con filtri
public function search(Request $request)
{
$query = Gestione::with(['stabile']);
if ($request->filled('anno')) {
$query->byAnno($request->anno);
}
if ($request->filled('stato')) {
$query->byStato($request->stato);
}
if ($request->filled('stabile_id')) {
$query->where('stabile_id', $request->stabile_id);
}
if ($request->filled('scadenze')) {
if ($request->scadenze === 'assemblee_prossime') {
$query->conAssembleaProssima();
} elseif ($request->scadenze === 'gestioni_scadute') {
$query->scadute();
}
}
$gestioni = $query->orderByDesc('anno_gestione')
->orderBy('denominazione')
->paginate(20);
return view('gestioni.search', compact('gestioni'));
}
```
---
## 🎛️ **FORM VALIDATION**
### **File**: `app/Http/Requests/GestioneRequest.php`
```php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class GestioneRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->check();
}
public function rules(): array
{
$gestioneId = $this->route('gestione')?->id;
$stabileId = $this->input('stabile_id');
return [
'stabile_id' => 'required|exists:stabili,id',
'anno_gestione' => [
'required',
'integer',
'min:2020',
'max:' . (date('Y') + 5),
Rule::unique('gestioni')
->where('stabile_id', $stabileId)
->ignore($gestioneId)
],
'data_inizio' => 'required|date',
'data_fine' => 'required|date|after:data_inizio',
'denominazione' => 'nullable|string|max:255',
'stato' => 'required|in:pianificata,attiva,chiusa,archiviata',
'budget_preventivo' => 'required|numeric|min:0|max:999999999.99',
'limite_spese_straordinarie' => 'nullable|numeric|min:0',
'percentuale_fondo_riserva' => 'required|numeric|min:0|max:50',
'rata_ordinaria_mensile' => 'nullable|numeric|min:0',
'data_approvazione_bilancio' => 'nullable|date',
'data_ultima_assemblea' => 'nullable|date',
'data_prossima_assemblea' => 'nullable|date|after:today',
'usa_fondo_di_riserva' => 'boolean',
'usa_millesimi_generali' => 'boolean',
'usa_millesimi_riscaldamento' => 'boolean',
'usa_millesimi_ascensore' => 'boolean',
];
}
public function messages(): array
{
return [
'stabile_id.required' => 'Devi selezionare uno stabile.',
'anno_gestione.required' => 'L\'anno di gestione è obbligatorio.',
'anno_gestione.unique' => 'Esiste già una gestione per questo anno e stabile.',
'data_inizio.required' => 'La data di inizio è obbligatoria.',
'data_fine.required' => 'La data di fine è obbligatoria.',
'data_fine.after' => 'La data di fine deve essere successiva alla data di inizio.',
'budget_preventivo.required' => 'Il budget preventivo è obbligatorio.',
'percentuale_fondo_riserva.max' => 'La percentuale del fondo di riserva non può superare il 50%.',
'data_prossima_assemblea.after' => 'La data della prossima assemblea deve essere futura.',
];
}
}
```
---
## 📋 **FORM BLADE**
### **File**: `resources/views/gestioni/form.blade.php`
```blade
<form method="POST" action="{{ isset($gestione) ? route('gestioni.update', $gestione) : route('gestioni.store') }}">
@csrf
@if(isset($gestione))
@method('PUT')
@endif
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Stabile -->
<div>
<label for="stabile_id" class="block text-sm font-medium text-gray-700">Stabile</label>
<select name="stabile_id" id="stabile_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="">Seleziona stabile...</option>
@foreach($stabili as $stabile)
<option value="{{ $stabile->id }}"
{{ old('stabile_id', $gestione->stabile_id ?? '') == $stabile->id ? 'selected' : '' }}>
{{ $stabile->denominazione }}
</option>
@endforeach
</select>
@error('stabile_id')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Anno Gestione -->
<div>
<label for="anno_gestione" class="block text-sm font-medium text-gray-700">Anno Gestione</label>
<input type="number"
name="anno_gestione"
id="anno_gestione"
min="2020"
max="{{ date('Y') + 5 }}"
value="{{ old('anno_gestione', $gestione->anno_gestione ?? date('Y')) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('anno_gestione')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Data Inizio -->
<div>
<label for="data_inizio" class="block text-sm font-medium text-gray-700">Data Inizio</label>
<input type="date"
name="data_inizio"
id="data_inizio"
value="{{ old('data_inizio', $gestione->data_inizio?->format('Y-m-d') ?? date('Y-01-01')) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('data_inizio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Data Fine -->
<div>
<label for="data_fine" class="block text-sm font-medium text-gray-700">Data Fine</label>
<input type="date"
name="data_fine"
id="data_fine"
value="{{ old('data_fine', $gestione->data_fine?->format('Y-m-d') ?? date('Y-12-31')) }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('data_fine')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Budget Preventivo -->
<div>
<label for="budget_preventivo" class="block text-sm font-medium text-gray-700">Budget Preventivo (€)</label>
<input type="number"
name="budget_preventivo"
id="budget_preventivo"
step="0.01"
min="0"
value="{{ old('budget_preventivo', $gestione->budget_preventivo ?? '') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('budget_preventivo')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Percentuale Fondo Riserva -->
<div>
<label for="percentuale_fondo_riserva" class="block text-sm font-medium text-gray-700">Fondo Riserva (%)</label>
<input type="number"
name="percentuale_fondo_riserva"
id="percentuale_fondo_riserva"
step="0.01"
min="0"
max="50"
value="{{ old('percentuale_fondo_riserva', $gestione->percentuale_fondo_riserva ?? '10.00') }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@error('percentuale_fondo_riserva')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Stato -->
<div>
<label for="stato" class="block text-sm font-medium text-gray-700">Stato</label>
<select name="stato" id="stato" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
@foreach(['pianificata', 'attiva', 'chiusa', 'archiviata'] as $stato)
<option value="{{ $stato }}"
{{ old('stato', $gestione->stato ?? 'pianificata') === $stato ? 'selected' : '' }}>
{{ ucfirst($stato) }}
</option>
@endforeach
</select>
</div>
<!-- Checkboxes Millesimi -->
<div class="md:col-span-2">
<fieldset>
<legend class="text-base font-medium text-gray-900">Configurazione Millesimi</legend>
<div class="mt-4 space-y-4">
<div class="flex items-center">
<input id="usa_millesimi_generali"
name="usa_millesimi_generali"
type="checkbox"
value="1"
{{ old('usa_millesimi_generali', $gestione->usa_millesimi_generali ?? true) ? 'checked' : '' }}
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="usa_millesimi_generali" class="ml-3 block text-sm font-medium text-gray-700">
Usa millesimi generali
</label>
</div>
<div class="flex items-center">
<input id="usa_millesimi_riscaldamento"
name="usa_millesimi_riscaldamento"
type="checkbox"
value="1"
{{ old('usa_millesimi_riscaldamento', $gestione->usa_millesimi_riscaldamento ?? false) ? 'checked' : '' }}
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="usa_millesimi_riscaldamento" class="ml-3 block text-sm font-medium text-gray-700">
Usa millesimi riscaldamento
</label>
</div>
<div class="flex items-center">
<input id="usa_millesimi_ascensore"
name="usa_millesimi_ascensore"
type="checkbox"
value="1"
{{ old('usa_millesimi_ascensore', $gestione->usa_millesimi_ascensore ?? false) ? 'checked' : '' }}
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="usa_millesimi_ascensore" class="ml-3 block text-sm font-medium text-gray-700">
Usa millesimi ascensore
</label>
</div>
</div>
</fieldset>
</div>
</div>
<!-- Submit Button -->
<div class="mt-6">
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{ isset($gestione) ? 'Aggiorna Gestione' : 'Crea Gestione' }}
</button>
</div>
</form>
```
---
## 🎯 **UTILIZZO PATTERN**
### **Nel Controller**:
```php
// Gestioni dello stabile corrente
$gestioni = Gestione::where('stabile_id', $stabile->id)
->orderByDesc('anno_gestione')
->get();
// Gestione attiva corrente
$gestioneAttiva = Gestione::byStato('attiva')
->where('stabile_id', $stabile->id)
->first();
// Assemblee in scadenza
$assembleeProssime = Gestione::conAssembleaProssima(15)->get();
```
### **Nei Blade Templates**:
```blade
{{-- Card gestione --}}
@foreach($gestioni as $gestione)
<div class="card border-l-4 border-{{ $gestione->stato_color }}-500">
<div class="flex justify-between items-center">
<h3>{{ $gestione->denominazione_computed }}</h3>
<span class="badge badge-{{ $gestione->stato_color }}">
{{ ucfirst($gestione->stato) }}
</span>
</div>
<div class="mt-2 text-sm text-gray-600">
<p>{{ $gestione->stabile->denominazione }}</p>
<p>{{ $gestione->data_inizio->format('d/m/Y') }} - {{ $gestione->data_fine->format('d/m/Y') }}</p>
<p>Budget: €{{ number_format($gestione->budget_preventivo, 2) }}</p>
@if($gestione->is_in_corso)
<div class="mt-2">
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full"
style="width: {{ $gestione->percentuale_completato }}%"></div>
</div>
<p class="text-xs mt-1">{{ round($gestione->percentuale_completato) }}% completato</p>
</div>
@endif
</div>
</div>
@endforeach
```
---
**🎯 TABELLA GESTIONI DEFINITA**
**Con periodi amministrativi, budget e isolamento per amministratore tramite stabili**
**📖 Prossimo: `unita-immobiliari.md` per appartamenti e locali collegati agli stabili**