'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; } }