'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; } public function getGestioneLabelAttribute(): string { $denominazione = trim((string) ($this->denominazione ?? '')); if ($denominazione !== '' && preg_match('/\b(19|20)\d{2}\s*[\/\-]\s*\d{2,4}\b/', $denominazione, $match)) { return preg_replace('/\s+/', '', $match[0]) ?: $denominazione; } if ($this->data_inizio && $this->data_fine) { $startYear = (int) $this->data_inizio->format('Y'); $endYear = (int) $this->data_fine->format('Y'); if ($startYear > 0 && $endYear > 0 && $startYear !== $endYear) { return sprintf('%04d/%02d', $startYear, $endYear % 100); } } return (string) $this->anno_gestione; } public function getPeriodoLabelAttribute(): string { $dal = $this->data_inizio?->format('d/m/Y'); $al = $this->data_fine?->format('d/m/Y'); if ($dal && $al) { return $dal . ' - ' . $al; } return trim(implode(' - ', array_filter([$dal, $al]))) ?: '—'; } public function getDisplayTitleAttribute(): string { $tipo = Str::headline((string) $this->tipo_gestione); if ($this->isStraordinaria() && $this->numero_straordinaria) { $tipo .= ' #' . $this->numero_straordinaria; } return $this->gestione_label . ' · ' . $tipo; } /** * 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; } }