feat: Complete millésimal tables system implementation

PHASE 2 COMPLETED: MILLÉSIMAL TABLES 

MODELS ENHANCED:
- TabellaMillesimale: Advanced methods for balance calculation, quota management
- DettaglioTabellaMillesimale: Validation, percentage calculation, expense quota
- Fixed Eloquent relationships with correct foreign keys (id instead of legacy keys)

FEATURES IMPLEMENTED:
 Automatic balance verification (totale_millesimi = 1000)
 Quota calculation for expense distribution
 Standard table types (proprietà_generale, scale, ascensore, etc.)
 Validation for positive millesimi values
 Automatic code generation for AnagraficaCondominiale (ANA prefix)
 Complete relationship testing (Amministratore→Stabili→Unità→Millesimi)

TESTING COMPLETED:
 Tabella millesimale creation and balance verification
 Unità immobiliari creation with correct field names
 Millesimi assignment and calculation (470.5882 + 529.4118 = 1000.0000)
 Expense quota calculation (€470.59 + €529.41 = €1000.00)
 Advanced features: riassunto, percentages, standard types

DATABASE STATUS:
- 1 Tabella Millesimale: 'Proprietà Generale' (balanced: SI)
- 2 Unità Immobiliari: Interno 1 & 2 with correct millesimi
- All relationships operational and tested

READY FOR PHASE 3: EXPENSE CATEGORIES & VOICE MANAGEMENT
This commit is contained in:
Pikappa2 2025-07-08 17:03:12 +02:00
parent f45845ba3c
commit 2e47dd8bc0
4 changed files with 194 additions and 29 deletions

View File

@ -58,14 +58,14 @@ class Amministratore extends Model
*/ */
public function stabili(): HasMany public function stabili(): HasMany
{ {
return $this->hasMany(Stabile::class, 'amministratore_id', 'id_amministratore'); return $this->hasMany(Stabile::class, 'amministratore_id', 'id');
} }
/** /**
* Get the fornitori for the Amministratore. * Get the fornitori for the Amministratore.
*/ */
public function fornitori(): HasMany public function fornitori(): HasMany
{ {
return $this->hasMany(Fornitore::class, 'amministratore_id', 'id_amministratore'); return $this->hasMany(Fornitore::class, 'amministratore_id', 'id');
} }
protected static function boot() protected static function boot()

View File

@ -42,6 +42,32 @@ class AnagraficaCondominiale extends Model
'ultima_sincronizzazione_google' 'ultima_sincronizzazione_google'
]; ];
/**
* Boot del modello per generazione automatica codice
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->codice_univoco)) {
$model->codice_univoco = self::generateUniqueCode();
}
});
}
/**
* Genera un codice univoco di 8 caratteri con prefisso ANA
*/
private static function generateUniqueCode()
{
do {
$code = 'ANA' . strtoupper(substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 5));
} while (self::where('codice_univoco', $code)->exists());
return $code;
}
protected $casts = [ protected $casts = [
'data_nascita' => 'date', 'data_nascita' => 'date',
'ultima_sincronizzazione_google' => 'datetime', 'ultima_sincronizzazione_google' => 'datetime',
@ -118,6 +144,14 @@ class AnagraficaCondominiale extends Model
return trim($this->nome . ' ' . $this->cognome); return trim($this->nome . ' ' . $this->cognome);
} }
/**
* Accessor per codice anagrafica (compatibilità)
*/
public function getCodiceAnagraficaAttribute()
{
return $this->codice_univoco;
}
/** /**
* Accessor per l'indirizzo completo di residenza * Accessor per l'indirizzo completo di residenza
*/ */
@ -229,32 +263,6 @@ class AnagraficaCondominiale extends Model
->exists(); ->exists();
} }
/**
* Boot method to generate automatic codes
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->codice_univoco)) {
$model->codice_univoco = $model->generateUniqueCode();
}
});
}
/**
* Generate a unique 8-character code for anagrafica
*/
public function generateUniqueCode()
{
do {
$code = 'ANA' . strtoupper(substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 5));
} while (self::where('codice_univoco', $code)->exists());
return $code;
}
/** /**
* Relazione con amministratore * Relazione con amministratore
*/ */

View File

@ -37,6 +37,66 @@ class DettaglioTabellaMillesimale extends Model
*/ */
public function unitaImmobiliare() public function unitaImmobiliare()
{ {
return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id');
}
/**
* Boot del modello per validazioni
*/
protected static function boot()
{
parent::boot();
static::saving(function ($model) {
// Validazione millesimi positivi
if ($model->millesimi < 0) {
throw new \InvalidArgumentException('I millesimi non possono essere negativi');
}
// Validazione millesimi ragionevoli (max 1000 per singola unità)
if ($model->millesimi > 1000) {
throw new \InvalidArgumentException('I millesimi per singola unità non possono superare 1000');
}
});
}
/**
* Scope per una specifica tabella millesimale
*/
public function scopeByTabella($query, $tabellaId)
{
return $query->where('tabella_millesimale_id', $tabellaId);
}
/**
* Scope per una specifica unità immobiliare
*/
public function scopeByUnita($query, $unitaId)
{
return $query->where('unita_immobiliare_id', $unitaId);
}
/**
* Accessor per percentuale (millesimi/10)
*/
public function getPercentualeAttribute()
{
return $this->millesimi / 10;
}
/**
* Accessor per millesimi formattati
*/
public function getMillesimiFormattatiAttribute()
{
return number_format($this->millesimi, 4, ',', '.');
}
/**
* Metodo per calcolare la quota di una spesa
*/
public function calcolaQuotaSpesa($importoTotale)
{
return ($importoTotale * $this->millesimi) / 1000;
} }
} }

View File

@ -68,4 +68,101 @@ class TabellaMillesimale extends Model
{ {
return $this->dettagli()->sum('millesimi'); return $this->dettagli()->sum('millesimi');
} }
/**
* Verifica se la tabella è bilanciata (totale = 1000)
*/
public function getIsBilanciataAttribute()
{
$totale = $this->totale_millesimi;
return abs($totale - 1000) < 0.0001; // Tolleranza per errori di precisione
}
/**
* Ottieni lo sbilanciamento della tabella
*/
public function getSbilanciamentoAttribute()
{
return $this->totale_millesimi - 1000;
}
/**
* Conta le unità immobiliari con millesimi assegnati
*/
public function getUnitaAssegnateAttribute()
{
return $this->dettagli()->count();
}
/**
* Accessor per riassunto tabella
*/
public function getRiassuntoAttribute()
{
return [
'nome' => $this->nome_tabella_millesimale,
'stabile' => $this->stabile->denominazione ?? 'N/A',
'unita_assegnate' => $this->unita_assegnate,
'totale_millesimi' => $this->totale_millesimi,
'is_bilanciata' => $this->is_bilanciata,
'sbilanciamento' => $this->sbilanciamento
];
}
/**
* Metodo per assegnare millesimi a un'unità immobiliare
*/
public function assegnaMillesimi($unitaImmobiliareId, $millesimi)
{
return DettaglioTabellaMillesimale::updateOrCreate(
[
'tabella_millesimale_id' => $this->id,
'unita_immobiliare_id' => $unitaImmobiliareId
],
[
'millesimi' => $millesimi
]
);
}
/**
* Metodo per bilanciare automaticamente la tabella
*/
public function bilancia()
{
$dettagli = $this->dettagli;
$totaleCorrente = $dettagli->sum('millesimi');
if ($totaleCorrente == 0) {
throw new \InvalidArgumentException('Impossibile bilanciare una tabella senza millesimi assegnati');
}
$fattoreCorrezione = 1000 / $totaleCorrente;
foreach ($dettagli as $dettaglio) {
$nuoviMillesimi = round($dettaglio->millesimi * $fattoreCorrezione, 4);
$dettaglio->update(['millesimi' => $nuoviMillesimi]);
}
return $this->fresh();
}
/**
* Ottieni tutte le tipologie standard di tabelle millesimali
*/
public static function getTipologieStandard()
{
return [
'proprieta_generale' => 'Proprietà Generale',
'scale' => 'Scale',
'ascensore' => 'Ascensore',
'riscaldamento' => 'Riscaldamento',
'acqua_calda' => 'Acqua Calda Sanitaria',
'condizionamento' => 'Condizionamento',
'garage' => 'Garage/Autorimesse',
'giardino' => 'Giardino',
'piscina' => 'Piscina',
'personalizzata' => 'Personalizzata'
];
}
} }