netgescon-master/app/Models/PianoRateizzazione.php
Pikappa2 bb38044019 feat: Implementazione completa sistema ripartizione spese e gestione rate
 Nuovo Sistema Ripartizione Spese:
- Migration e modelli RipartizioneSpese, DettaglioRipartizioneSpese
- Calcolo automatico ripartizione millesimale
- Gestione quote personalizzate ed esenzioni
- Stati workflow: bozza → confermata → contabilizzata
- Integrazione con tabelle millesimali e voci spesa

 Nuovo Sistema Gestione Rate:
- Migration e modelli PianoRateizzazione, Rata (aggiornato)
- Generazione automatica rate per piani di rateizzazione
- Gestione pagamenti completi e parziali
- Frequenze: mensile, trimestrale, semestrale, personalizzata
- Monitoraggio scadenze e stati rate

🔧 Funzionalità Avanzate:
- Codici automatici univoci (RS*, PR*, RT*)
- Relazioni complete tra tutti i modelli
- Scope e query builder avanzati
- Statistiche e reporting
- Backward compatibility con vecchia struttura

�� Test e Integrazione:
- Test modelli e database completati
- Relazioni Eloquent integrate
- Metodi di calcolo validati
- Sistema pronto per produzione
2025-07-08 17:42:01 +02:00

398 lines
10 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use Carbon\Carbon;
/**
* Class PianoRateizzazione
*
* Gestisce i piani di rateizzazione per i pagamenti condominiali
*
* @package App\Models
*/
class PianoRateizzazione extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'piano_rateizzazione';
protected $fillable = [
'codice_piano',
'stabile_id',
'descrizione',
'tipo_piano',
'importo_totale',
'numero_rate',
'data_prima_rata',
'frequenza',
'stato',
'configurazione_rate',
'note',
'creato_da',
'attivato_at',
'attivato_da'
];
protected $casts = [
'importo_totale' => 'decimal:2',
'numero_rate' => 'integer',
'data_prima_rata' => 'date',
'configurazione_rate' => 'array',
'attivato_at' => 'datetime'
];
protected $dates = [
'data_prima_rata',
'attivato_at',
'created_at',
'updated_at',
'deleted_at'
];
/**
* Boot method per generare automaticamente il codice piano
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->codice_piano)) {
$model->codice_piano = self::generaCodicePiano();
}
});
}
/**
* Genera un codice univoco per il piano
*/
public static function generaCodicePiano()
{
do {
$codice = 'PR' . strtoupper(Str::random(6));
} while (self::where('codice_piano', $codice)->exists());
return $codice;
}
/**
* Relazione con Stabile
*/
public function stabile()
{
return $this->belongsTo(Stabile::class);
}
/**
* Relazione con User (creato da)
*/
public function creatoDa()
{
return $this->belongsTo(User::class, 'creato_da');
}
/**
* Relazione con User (attivato da)
*/
public function attivatoDa()
{
return $this->belongsTo(User::class, 'attivato_da');
}
/**
* Relazione con Rate
*/
public function rate()
{
return $this->hasMany(Rata::class, 'piano_rateizzazione_id');
}
/**
* Scope per piani per stabile
*/
public function scopePerStabile($query, $stabileId)
{
return $query->where('stabile_id', $stabileId);
}
/**
* Scope per stato
*/
public function scopeConStato($query, $stato)
{
return $query->where('stato', $stato);
}
/**
* Scope per tipo piano
*/
public function scopePerTipo($query, $tipo)
{
return $query->where('tipo_piano', $tipo);
}
/**
* Scope per frequenza
*/
public function scopePerFrequenza($query, $frequenza)
{
return $query->where('frequenza', $frequenza);
}
/**
* Metodo per attivare il piano
*/
public function attiva($userId = null)
{
$this->update([
'stato' => 'attivo',
'attivato_at' => now(),
'attivato_da' => $userId ?? auth()->id()
]);
// Genera le rate se non esistono
if ($this->rate()->count() == 0) {
$this->generaRate();
}
return $this;
}
/**
* Metodo per sospendere il piano
*/
public function sospendi()
{
$this->update(['stato' => 'sospeso']);
return $this;
}
/**
* Metodo per annullare il piano
*/
public function annulla()
{
$this->update(['stato' => 'annullato']);
// Annulla tutte le rate non pagate
$this->rate()->whereIn('stato', ['emessa', 'scaduta'])->update(['stato' => 'annullata']);
return $this;
}
/**
* Metodo per completare il piano
*/
public function completa()
{
$this->update(['stato' => 'completato']);
return $this;
}
/**
* Verifica se può essere modificato
*/
public function puoEssereModificato()
{
return in_array($this->stato, ['bozza']);
}
/**
* Verifica se può essere eliminato
*/
public function puoEssereEliminato()
{
return in_array($this->stato, ['bozza', 'annullato']);
}
/**
* Genera le rate del piano
*/
public function generaRate()
{
if ($this->numero_rate <= 0) {
throw new \Exception('Numero rate deve essere maggiore di 0');
}
// Elimina le rate esistenti se ci sono
$this->rate()->delete();
$unitaImmobiliari = $this->stabile->unitaImmobiliari;
$importoPerRata = $this->importo_totale / $this->numero_rate;
for ($numeroRata = 1; $numeroRata <= $this->numero_rate; $numeroRata++) {
$dataScadenza = $this->calcolaDataScadenzaRata($numeroRata);
foreach ($unitaImmobiliari as $unita) {
$this->rate()->create([
'codice_rata' => Rata::generaCodiceRata(),
'unita_immobiliare_id' => $unita->id,
'anagrafica_condominiale_id' => $unita->anagrafiche()->where('tipo_diritto', 'proprietario')->first()?->id,
'numero_rata' => $numeroRata,
'descrizione' => "{$this->descrizione} - Rata {$numeroRata}/{$this->numero_rate}",
'importo_rata' => $importoPerRata,
'data_scadenza' => $dataScadenza,
'importo_residuo' => $importoPerRata,
'stato' => 'emessa'
]);
}
}
return $this;
}
/**
* Calcola la data di scadenza per una rata specifica
*/
public function calcolaDataScadenzaRata($numeroRata)
{
$dataBase = Carbon::parse($this->data_prima_rata);
$mesiDaAggiungere = match ($this->frequenza) {
'mensile' => ($numeroRata - 1) * 1,
'bimestrale' => ($numeroRata - 1) * 2,
'trimestrale' => ($numeroRata - 1) * 3,
'semestrale' => ($numeroRata - 1) * 6,
'annuale' => ($numeroRata - 1) * 12,
'personalizzata' => $this->calcolaIntervalloPersonalizzato($numeroRata),
default => ($numeroRata - 1) * 1
};
return $dataBase->addMonths($mesiDaAggiungere);
}
/**
* Calcola intervallo personalizzato (da implementare in base alla configurazione)
*/
private function calcolaIntervalloPersonalizzato($numeroRata)
{
$config = $this->configurazione_rate;
if (isset($config['intervalli']) && is_array($config['intervalli'])) {
return $config['intervalli'][$numeroRata - 1] ?? 0;
}
return ($numeroRata - 1) * 1; // Default mensile
}
/**
* Ottieni statistiche del piano
*/
public function getStatistiche()
{
$rateQuery = $this->rate();
return [
'totale_rate' => $rateQuery->count(),
'rate_pagate' => $rateQuery->where('stato', 'pagata')->count(),
'rate_scadute' => $rateQuery->where('stato', 'scaduta')->count(),
'importo_pagato' => $rateQuery->sum('importo_pagato'),
'importo_residuo' => $rateQuery->sum('importo_residuo'),
'percentuale_completamento' => $this->getPercentualeCompletamento(),
'prossima_scadenza' => $rateQuery->where('stato', 'emessa')->min('data_scadenza')
];
}
/**
* Calcola percentuale di completamento
*/
public function getPercentualeCompletamento()
{
if ($this->importo_totale == 0) {
return 0;
}
$importoPagato = $this->rate()->sum('importo_pagato');
return round(($importoPagato / $this->importo_totale) * 100, 2);
}
/**
* Verifica se il piano è completato
*/
public function isCompletato()
{
return $this->rate()->where('stato', '!=', 'pagata')->count() == 0;
}
/**
* Verifica se ci sono rate scadute
*/
public function hasRateScadute()
{
return $this->rate()
->where('stato', 'emessa')
->where('data_scadenza', '<', now()->toDateString())
->exists();
}
/**
* Aggiorna automaticamente lo stato del piano
*/
public function aggiornaStato()
{
if ($this->stato === 'attivo') {
if ($this->isCompletato()) {
$this->completa();
} elseif ($this->hasRateScadute()) {
// Aggiorna le rate scadute
$this->rate()
->where('stato', 'emessa')
->where('data_scadenza', '<', now()->toDateString())
->update(['stato' => 'scaduta']);
}
}
return $this;
}
/**
* Accessor per tipo piano formattato
*/
public function getTipoPianoFormattatoAttribute()
{
return match ($this->tipo_piano) {
'ordinario' => 'Piano Ordinario',
'straordinario' => 'Piano Straordinario',
'conguaglio' => 'Piano Conguaglio',
'personalizzato' => 'Piano Personalizzato',
default => ucfirst($this->tipo_piano)
};
}
/**
* Accessor per frequenza formattata
*/
public function getFrequenzaFormattataAttribute()
{
return match ($this->frequenza) {
'mensile' => 'Mensile',
'bimestrale' => 'Bimestrale',
'trimestrale' => 'Trimestrale',
'semestrale' => 'Semestrale',
'annuale' => 'Annuale',
'personalizzata' => 'Personalizzata',
default => ucfirst($this->frequenza)
};
}
/**
* Accessor per stato formattato
*/
public function getStatoFormattatoAttribute()
{
return match ($this->stato) {
'bozza' => 'Bozza',
'attivo' => 'Attivo',
'completato' => 'Completato',
'sospeso' => 'Sospeso',
'annullato' => 'Annullato',
default => ucfirst($this->stato)
};
}
}