✨ 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
398 lines
10 KiB
PHP
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)
|
|
};
|
|
}
|
|
}
|