459 lines
13 KiB
PHP
459 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Carbon\Carbon;
|
|
|
|
/**
|
|
* MODEL: GestioneContabile
|
|
*
|
|
* Gestisce separazione contabile multi-anno e multi-gestione
|
|
* Ogni gestione ha il proprio protocollo e bilancio
|
|
*/
|
|
class GestioneContabile extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $table = 'gestioni_contabili';
|
|
|
|
protected $fillable = [
|
|
'tenant_id',
|
|
'stabile_id',
|
|
'anno_gestione',
|
|
'tipo_gestione',
|
|
'numero_straordinaria',
|
|
'denominazione',
|
|
'data_inizio',
|
|
'data_fine',
|
|
'stato',
|
|
'bilancio_apertura',
|
|
'bilancio_chiusura',
|
|
'protocollo_prefix',
|
|
'ultimo_protocollo',
|
|
'percentuale_fondo_riserva',
|
|
'rata_ordinaria_mensile',
|
|
'usa_millesimi_generali',
|
|
'usa_millesimi_riscaldamento',
|
|
'usa_millesimi_ascensore',
|
|
'configurazione',
|
|
'note_gestione',
|
|
'gestione_attiva',
|
|
'mesi_rate_ordinaria',
|
|
'mesi_rate_riscaldamento',
|
|
'mesi_rate_straordinaria',
|
|
'codice_archivio_legacy',
|
|
'piano_straordinario'
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_inizio' => 'date',
|
|
'data_fine' => 'date',
|
|
'bilancio_apertura' => 'array',
|
|
'bilancio_chiusura' => 'array',
|
|
'configurazione' => 'array',
|
|
'gestione_attiva' => 'boolean',
|
|
'usa_millesimi_generali' => 'boolean',
|
|
'usa_millesimi_riscaldamento' => 'boolean',
|
|
'usa_millesimi_ascensore' => 'boolean',
|
|
'percentuale_fondo_riserva' => 'decimal:2',
|
|
'rata_ordinaria_mensile' => 'decimal:2',
|
|
'mesi_rate_ordinaria' => 'array',
|
|
'mesi_rate_riscaldamento' => 'array',
|
|
'mesi_rate_straordinaria' => 'array',
|
|
'piano_straordinario' => 'array'
|
|
];
|
|
|
|
/** Imposta pianificazione mesi da array di checkbox (chiave = mese, valore truthy) */
|
|
public function setMesiFromCheckboxes(string $tipo, array $input): void
|
|
{
|
|
$mesi = [];
|
|
foreach (range(1, 12) as $m) {
|
|
if (!empty($input[(string)$m]) || !empty($input[$m])) {
|
|
$mesi[] = (int)$m;
|
|
}
|
|
}
|
|
$field = match ($tipo) {
|
|
'ordinaria' => 'mesi_rate_ordinaria',
|
|
'riscaldamento' => 'mesi_rate_riscaldamento',
|
|
'straordinaria' => 'mesi_rate_straordinaria',
|
|
default => 'mesi_rate_ordinaria',
|
|
};
|
|
$this->{$field} = $mesi;
|
|
}
|
|
|
|
/** Relazione con stabile */
|
|
public function stabile()
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
/**
|
|
* Operazioni contabili di questa gestione
|
|
*/
|
|
public function operazioni(): HasMany
|
|
{
|
|
return $this->hasMany(OperazioneContabile::class, 'gestione_id');
|
|
}
|
|
|
|
/**
|
|
* Incassi di questa gestione
|
|
*/
|
|
public function incassi(): HasMany
|
|
{
|
|
return $this->hasMany(Incasso::class, 'gestione_id');
|
|
}
|
|
|
|
/**
|
|
* Ritenute d'acconto di questa gestione
|
|
*/
|
|
public function ritenuteAcconto(): HasMany
|
|
{
|
|
return $this->hasMany(RegistroRitenuteAcconto::class, 'gestione_id');
|
|
}
|
|
|
|
/**
|
|
* Incassi estratto conto di questa gestione
|
|
*/
|
|
public function incassiEstrattoConto(): HasMany
|
|
{
|
|
return $this->hasMany(IncassoEstrattoConto::class, 'gestione_id');
|
|
}
|
|
|
|
/**
|
|
* Scope per tenant
|
|
*/
|
|
public function scopeForTenant($query, string $tenantId)
|
|
{
|
|
return $query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
/**
|
|
* Scope per anno
|
|
*/
|
|
public function scopeAnno($query, int $anno)
|
|
{
|
|
return $query->where('anno_gestione', $anno);
|
|
}
|
|
|
|
/**
|
|
* Scope per tipo gestione
|
|
*/
|
|
public function scopeTipo($query, string $tipo)
|
|
{
|
|
return $query->where('tipo_gestione', $tipo);
|
|
}
|
|
|
|
/**
|
|
* Scope gestioni attive
|
|
*/
|
|
public function scopeAttive($query)
|
|
{
|
|
return $query->where('gestione_attiva', true);
|
|
}
|
|
|
|
/**
|
|
* Scope gestioni aperte
|
|
*/
|
|
public function scopeAperte($query)
|
|
{
|
|
return $query->where('stato', 'aperta');
|
|
}
|
|
|
|
/**
|
|
* Verifica se la gestione è attiva
|
|
*/
|
|
public function isAttiva(): bool
|
|
{
|
|
return $this->gestione_attiva && $this->stato === 'aperta';
|
|
}
|
|
|
|
/**
|
|
* Verifica se la gestione è chiusa
|
|
*/
|
|
public function isChiusa(): bool
|
|
{
|
|
return in_array($this->stato, ['chiusa', 'consolidata']);
|
|
}
|
|
|
|
/**
|
|
* Verifica se è una gestione straordinaria
|
|
*/
|
|
public function isStraordinaria(): bool
|
|
{
|
|
return $this->tipo_gestione === 'straordinaria';
|
|
}
|
|
|
|
/** Verifica se è gestione ordinaria */
|
|
public function isOrdinaria(): bool
|
|
{
|
|
return $this->tipo_gestione === 'ordinaria';
|
|
}
|
|
|
|
/** Verifica se è gestione riscaldamento */
|
|
public function isRiscaldamento(): bool
|
|
{
|
|
return $this->tipo_gestione === 'riscaldamento';
|
|
}
|
|
|
|
/**
|
|
* Genera prossimo numero protocollo
|
|
*/
|
|
public function getNextProtocollo(): array
|
|
{
|
|
$this->increment('ultimo_protocollo');
|
|
|
|
$numero = $this->ultimo_protocollo;
|
|
$completo = $this->isStraordinaria()
|
|
? "{$this->protocollo_prefix}-{$numero}"
|
|
: "{$this->protocollo_prefix}-{$numero}";
|
|
|
|
return [
|
|
'numero' => $numero,
|
|
'completo' => $completo,
|
|
'prefix' => $this->protocollo_prefix
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Calcola totali gestione
|
|
*/
|
|
public function calcolaTotali(): array
|
|
{
|
|
$operazioni = $this->operazioni();
|
|
|
|
return [
|
|
'totale_entrate' => $operazioni->where('natura2', 'LIKE', 'E%')->sum('importo'),
|
|
'totale_uscite' => $operazioni->where('natura2', 'LIKE', 'U%')->sum('importo'),
|
|
'totale_operazioni' => $operazioni->count(),
|
|
'saldo_gestione' => $operazioni->sum('dare') - $operazioni->sum('avere'),
|
|
'ultimo_protocollo' => $this->ultimo_protocollo
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ottiene statistiche riconciliazione
|
|
*/
|
|
public function getStatisticheRiconciliazione(): array
|
|
{
|
|
$incassi = $this->incassi();
|
|
$incassiEc = $this->incassiEstrattoConto();
|
|
|
|
return [
|
|
'incassi_totali' => $incassi->count(),
|
|
'incassi_riconciliati' => $incassi->whereNotNull('movimento_bancario_id')->count(),
|
|
'incassi_ec_totali' => $incassiEc->count(),
|
|
'incassi_ec_riconciliati' => $incassiEc->where('stato', 'riconciliato')->count(),
|
|
'percentuale_riconciliazione' => $incassi->count() > 0
|
|
? round(($incassi->whereNotNull('movimento_bancario_id')->count() / $incassi->count()) * 100, 2)
|
|
: 0
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Chiude la gestione
|
|
*/
|
|
public function chiudiGestione(): void
|
|
{
|
|
if ($this->stato !== 'aperta') {
|
|
throw new \Exception("La gestione non è aperta e non può essere chiusa");
|
|
}
|
|
|
|
// Calcola bilancio di chiusura
|
|
$bilancioDiChiusura = $this->calcolaBilancioChiusura();
|
|
|
|
$this->update([
|
|
'stato' => 'chiusa',
|
|
'gestione_attiva' => false,
|
|
'bilancio_chiusura' => $bilancioDiChiusura
|
|
]);
|
|
|
|
// Crea bilancio di apertura per gestione successiva
|
|
$this->createBilancioAperturaSuccessiva($bilancioDiChiusura);
|
|
}
|
|
|
|
/**
|
|
* Calcola bilancio di chiusura
|
|
*/
|
|
private function calcolaBilancioChiusura(): array
|
|
{
|
|
$operazioni = $this->operazioni()->where('stato_operazione', 'confermata')->get();
|
|
|
|
$bilancio = [];
|
|
|
|
// Raggruppa per conto contabile
|
|
foreach ($operazioni as $operazione) {
|
|
$conto = $operazione->conto_contabile ?? 'GENERICO';
|
|
|
|
if (!isset($bilancio[$conto])) {
|
|
$bilancio[$conto] = ['dare' => 0, 'avere' => 0, 'saldo' => 0];
|
|
}
|
|
|
|
$bilancio[$conto]['dare'] += $operazione->dare;
|
|
$bilancio[$conto]['avere'] += $operazione->avere;
|
|
$bilancio[$conto]['saldo'] = $bilancio[$conto]['dare'] - $bilancio[$conto]['avere'];
|
|
}
|
|
|
|
return [
|
|
'data_chiusura' => now(),
|
|
'conti' => $bilancio,
|
|
'totale_dare' => array_sum(array_column(array_column($bilancio, 'dare'), 0)),
|
|
'totale_avere' => array_sum(array_column(array_column($bilancio, 'avere'), 0)),
|
|
'saldo_finale' => array_sum(array_column(array_column($bilancio, 'saldo'), 0))
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Crea bilancio apertura per gestione successiva
|
|
*/
|
|
private function createBilancioAperturaSuccessiva(array $bilancioChiusura): void
|
|
{
|
|
$annoSuccessivo = $this->anno_gestione + 1;
|
|
|
|
// Cerca gestione successiva dello stesso tipo
|
|
$gestioneSuccessiva = static::where('tenant_id', $this->tenant_id)
|
|
->where('anno_gestione', $annoSuccessivo)
|
|
->where('tipo_gestione', $this->tipo_gestione)
|
|
->where('numero_straordinaria', $this->numero_straordinaria)
|
|
->first();
|
|
|
|
if ($gestioneSuccessiva) {
|
|
// Trasforma bilancio chiusura in apertura
|
|
$bilanciOApertura = [];
|
|
foreach ($bilancioChiusura['conti'] as $conto => $dati) {
|
|
if ($dati['saldo'] != 0) {
|
|
$bilanciOApertura[$conto] = [
|
|
'saldo_iniziale' => $dati['saldo'],
|
|
'da_gestione_precedente' => $this->id
|
|
];
|
|
}
|
|
}
|
|
|
|
$gestioneSuccessiva->update([
|
|
'bilancio_apertura' => [
|
|
'data_apertura' => $gestioneSuccessiva->data_inizio,
|
|
'da_gestione' => $this->id,
|
|
'conti' => $bilanciOApertura
|
|
]
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Formatta denominazione per display
|
|
*/
|
|
public function getDenominazioneCompleta(): string
|
|
{
|
|
$base = $this->denominazione;
|
|
|
|
if ($this->isStraordinaria()) {
|
|
$base .= " (N.{$this->numero_straordinaria})";
|
|
}
|
|
|
|
if (!$this->isAttiva()) {
|
|
$base .= " [CHIUSA]";
|
|
}
|
|
|
|
return $base;
|
|
}
|
|
|
|
/**
|
|
* Colore per stato gestione
|
|
*/
|
|
public function getColoreStato(): string
|
|
{
|
|
return match ($this->stato) {
|
|
'aperta' => 'success',
|
|
'chiusa' => 'warning',
|
|
'consolidata' => 'secondary',
|
|
default => 'light'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Icona per tipo gestione
|
|
*/
|
|
public function getIconaTipo(): string
|
|
{
|
|
return match ($this->tipo_gestione) {
|
|
'ordinaria' => 'calendar-check',
|
|
'riscaldamento' => 'thermometer-half',
|
|
'straordinaria' => 'exclamation-triangle',
|
|
default => 'folder'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Info completa per dashboard
|
|
*/
|
|
public function getInfoCompleta(): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'denominazione' => $this->getDenominazioneCompleta(),
|
|
'anno_gestione' => $this->anno_gestione,
|
|
'tipo_gestione' => $this->tipo_gestione,
|
|
'numero_straordinaria' => $this->numero_straordinaria,
|
|
'stato' => $this->stato,
|
|
'colore_stato' => $this->getColoreStato(),
|
|
'icona_tipo' => $this->getIconaTipo(),
|
|
'attiva' => $this->isAttiva(),
|
|
'protocollo_prefix' => $this->protocollo_prefix,
|
|
'ultimo_protocollo' => $this->ultimo_protocollo,
|
|
'periodo' => [
|
|
'inizio' => $this->data_inizio->format('d/m/Y'),
|
|
'fine' => $this->data_fine->format('d/m/Y')
|
|
],
|
|
'totali' => $this->calcolaTotali(),
|
|
'riconciliazione' => $this->getStatisticheRiconciliazione()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Crea gestioni standard per un anno
|
|
*/
|
|
public static function createGestioniAnno(string $tenantId, int $anno): array
|
|
{
|
|
$gestioni = [];
|
|
|
|
// Ordinaria
|
|
$gestioni['ordinaria'] = static::create([
|
|
'tenant_id' => $tenantId,
|
|
'anno_gestione' => $anno,
|
|
'tipo_gestione' => 'ordinaria',
|
|
'denominazione' => "Gestione Ordinaria {$anno}",
|
|
'data_inizio' => "{$anno}-01-01",
|
|
'data_fine' => "{$anno}-12-31",
|
|
'protocollo_prefix' => "O{$anno}",
|
|
'stato' => 'aperta'
|
|
]);
|
|
|
|
// Riscaldamento
|
|
$gestioni['riscaldamento'] = static::create([
|
|
'tenant_id' => $tenantId,
|
|
'anno_gestione' => $anno,
|
|
'tipo_gestione' => 'riscaldamento',
|
|
'denominazione' => "Gestione Riscaldamento {$anno}",
|
|
'data_inizio' => "{$anno}-01-01",
|
|
'data_fine' => "{$anno}-12-31",
|
|
'protocollo_prefix' => "R{$anno}",
|
|
'stato' => 'aperta'
|
|
]);
|
|
|
|
return $gestioni;
|
|
}
|
|
|
|
/** Variante con stabile */
|
|
public static function createGestioniAnnoForStabile(string $tenantId, int $stabileId, int $anno): array
|
|
{
|
|
$gestioni = static::createGestioniAnno($tenantId, $anno);
|
|
foreach ($gestioni as $g) {
|
|
$g->stabile_id = $stabileId;
|
|
$g->save();
|
|
}
|
|
return $gestioni;
|
|
}
|
|
}
|