Map cartella legacy (0001) -> label utente (es. 2024-25) */ public array $legacyYearLabels = []; public array $conti = []; public array $risorseFinanziarie = []; public array $bilancio = []; public array $conguagliExcel = []; public array $conguagliExcelCols = []; // CRUD stile "Excel" (dati legacy su gescon_import) public array $creDebRows = []; /** @var array> */ public array $creDebEdit = []; public array $formCreDebAdd = [ 'cod_voc' => null, 'des_voce' => null, 'descrizione' => null, 'importo_euro' => null, 'n_stra' => null, 'incluso' => 1, ]; public array $dettTabRows = []; /** @var array> */ public array $dettTabEdit = []; public array $formDettTabAdd = [ 'cod_tab' => 'CONG.O', 'id_cond' => null, 'cond_inquil' => null, 'mm' => null, 'cons_euro' => null, 'n_stra' => null, 'unico' => 0, ]; public array $formVoce = [ 'gestione' => 'ordinaria', 'categoria' => 'crediti', 'n_stra' => null, 'codice' => null, 'descrizione' => null, 'importo_euro' => null, ]; public array $formConguaglio = [ 'cod_tab' => 'CONG.O', 'n_stra' => null, 'id_cond' => null, 'cons_euro' => null, ]; public array $detailMeta = []; public array $detailRows = []; /** @var array */ public array $vocSpeOptions = []; // Editing "a riga" (riduce la pagina: si modifica una riga alla volta) public ?int $editingCreDebId = null; public ?int $editingDettTabId = null; // Selezione riga (per mostrare dettaglio voce spesa sotto Crediti/Debiti) public ?int $selectedCreDebId = null; // Sub-tab documenti (sotto Crediti/Debiti): voce / FE / RA public string $detailTab = 'voce'; /** @var array> */ public array $feRows = []; /** @var array */ public array $linkedFeIds = []; /** @var array> */ public array $raRows = []; /** @var array */ public array $linkedRaIds = []; public static function canAccess(): bool { $user = Auth::user(); if (! $user instanceof User) { return false; } if ($user->hasAnyRole(['super-admin', 'admin'])) { return true; } return $user->can('contabilita.prima-nota'); } public function mount(): void { $this->legacyYear = is_string($this->legacyYear) ? trim((string) $this->legacyYear) : null; $this->legacyYear = $this->legacyYear !== '' ? $this->legacyYear : null; $this->dataBilancio = is_string($this->dataBilancio) ? trim((string) $this->dataBilancio) : null; $this->dataBilancio = $this->dataBilancio !== '' ? $this->dataBilancio : null; $this->saldoBancaManuale = is_string($this->saldoBancaManuale) ? trim((string) $this->saldoBancaManuale) : null; $this->saldoBancaManuale = $this->saldoBancaManuale !== '' ? $this->saldoBancaManuale : null; $this->tab = is_string($this->tab) ? trim((string) $this->tab) : 'bilancio'; $this->tab = in_array($this->tab, ['bilancio', 'crediti', 'debiti', 'conguagli', 'dettaglio'], true) ? $this->tab : 'bilancio'; $this->dett = is_string($this->dett) ? trim((string) $this->dett) : null; $this->dett = $this->dett !== '' ? $this->dett : null; $this->reloadData(); } public function updatedTab(): void { $this->tab = is_string($this->tab) ? trim((string) $this->tab) : 'bilancio'; $this->tab = in_array($this->tab, ['bilancio', 'crediti', 'debiti', 'conguagli', 'dettaglio'], true) ? $this->tab : 'bilancio'; if ($this->tab !== 'dettaglio') { $this->dett = null; } // Reset stati per evitare UI "incastrata" cambiando tab $this->editingCreDebId = null; $this->editingDettTabId = null; $this->selectedCreDebId = null; $this->detailTab = 'voce'; $this->reloadData(); } public function editCreDebRow(int $id): void { if ($id <= 0) { return; } $this->editingCreDebId = $id; $this->selectedCreDebId = $id; } public function cancelEditCreDebRow(): void { $this->editingCreDebId = null; } public function selectCreDebRow(int $id): void { if ($id <= 0) { $this->selectedCreDebId = null; return; } $this->selectedCreDebId = $id; // Aggiorna subito i collegamenti documentali per la riga selezionata $this->reloadDocumenti(); } public function setDetailTab(string $tab): void { $tab = strtolower(trim($tab)); $this->detailTab = in_array($tab, ['voce', 'fe', 'ra'], true) ? $tab : 'voce'; } public function editDettTabRow(int $id): void { if ($id <= 0) { return; } $this->editingDettTabId = $id; } public function cancelEditDettTabRow(): void { $this->editingDettTabId = null; } private function loadCondominiLabels(string $codStabileLegacy, ?string $legacyYear): array { $out = []; // Tabella "condomini" (più snella) se presente if (Schema::connection('gescon_import')->hasTable('condomini')) { $q = DB::connection('gescon_import')->table('condomini') ->where('cod_stabile', $codStabileLegacy); if ($legacyYear && Schema::connection('gescon_import')->hasColumn('condomini', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $rows = $q->get(['id_cond', 'cognome', 'nome', 'cod_cond']); foreach ($rows as $r) { $id = trim((string) ($r->id_cond ?? '')); if ($id === '') { continue; } $label = trim(trim((string) ($r->cognome ?? '')) . ' ' . trim((string) ($r->nome ?? ''))); if ($label === '') { $label = trim((string) ($r->cod_cond ?? '')); } $out[$id] = $label !== '' ? $label : $id; } } // Fallback su "condomin" (più completa) if (empty($out) && Schema::connection('gescon_import')->hasTable('condomin')) { $q = DB::connection('gescon_import')->table('condomin') ->where('cod_stabile', $codStabileLegacy); if ($legacyYear && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $rows = $q->get(['id_cond', 'denominazione', 'nom_cond', 'cognome', 'nome', 'cod_cond']); foreach ($rows as $r) { $id = trim((string) ($r->id_cond ?? '')); if ($id === '') { continue; } $label = trim((string) ($r->denominazione ?? '')); if ($label === '') { $label = trim((string) ($r->nom_cond ?? '')); } if ($label === '') { $label = trim(trim((string) ($r->cognome ?? '')) . ' ' . trim((string) ($r->nome ?? ''))); } if ($label === '') { $label = trim((string) ($r->cod_cond ?? '')); } $out[$id] = $label !== '' ? $label : $id; } } return $out; } public function updatedLegacyYear(): void { $this->legacyYear = is_string($this->legacyYear) ? trim((string) $this->legacyYear) : null; $this->legacyYear = $this->legacyYear !== '' ? $this->legacyYear : null; $this->reloadData(); } public function updatedDataBilancio(): void { $this->dataBilancio = is_string($this->dataBilancio) ? trim((string) $this->dataBilancio) : null; $this->dataBilancio = $this->dataBilancio !== '' ? $this->dataBilancio : null; $this->reloadData(); } public function updatedSaldoBancaManuale(): void { $this->saldoBancaManuale = is_string($this->saldoBancaManuale) ? trim((string) $this->saldoBancaManuale) : null; $this->saldoBancaManuale = $this->saldoBancaManuale !== '' ? $this->saldoBancaManuale : null; $this->reloadData(); } public function updatedDett(): void { $this->dett = is_string($this->dett) ? trim((string) $this->dett) : null; $this->dett = $this->dett !== '' ? $this->dett : null; $this->reloadData(); } private function getActiveStabile(): ?Stabile { $user = Auth::user(); if (! $user instanceof User) { return null; } return StabileContext::getActiveStabile($user); } private function reloadData(): void { $stabile = $this->getActiveStabile(); $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; $this->legacyYears = $this->loadLegacyYears($codStabileLegacy); $this->legacyYearLabels = $codStabileLegacy ? $this->loadLegacyYearLabels($codStabileLegacy) : []; // Default/fallback legacyYear: // - Se l'utente NON ha passato legacy_year in query, default al primo esercizio (tipicamente 0001). // - Se legacy_year è passato (anche vuoto => "Tutti"), rispettalo. // - Se legacy_year è valorizzato ma non esiste più tra quelli disponibili, fai fallback al primo disponibile. $hasLegacyYearParam = request()->query->has('legacy_year'); if (! $hasLegacyYearParam) { if (! empty($this->legacyYears) && ($this->legacyYear === null || $this->legacyYear === '')) { $this->legacyYear = $this->legacyYears[0]; } } if ($this->legacyYear !== null && $this->legacyYear !== '' && ! in_array($this->legacyYear, $this->legacyYears, true)) { $this->legacyYear = ! empty($this->legacyYears) ? $this->legacyYears[0] : null; } $stabileId = $stabile?->id ? (int) $stabile->id : null; $this->conti = $stabileId ? $this->loadContiOptions($stabileId) : []; if (! $this->dataBilancio) { $this->dataBilancio = $this->defaultDataBilancio(); } if (! $codStabileLegacy || ! $stabileId) { $this->bilancio = $this->emptyBilancio(); $this->risorseFinanziarie = []; $this->conguagliExcel = []; $this->conguagliExcelCols = []; $this->detailMeta = []; $this->detailRows = []; return; } $this->risorseFinanziarie = $this->computeRisorseFinanziarie($stabileId, $this->dataBilancio); $this->bilancio = $this->computeBilancio($stabileId, $codStabileLegacy, $this->legacyYear, $this->dataBilancio, $this->saldoBancaManuale, $this->risorseFinanziarie); $this->vocSpeOptions = $this->loadVocSpeOptions($codStabileLegacy, $this->legacyYear); if ($this->tab === 'conguagli') { [$this->conguagliExcelCols, $this->conguagliExcel] = $this->computeConguagliExcel($codStabileLegacy, $this->legacyYear); [$this->dettTabRows, $this->dettTabEdit] = $this->loadDettTabCrudRows($codStabileLegacy, $this->legacyYear); } else { $this->conguagliExcel = []; $this->conguagliExcelCols = []; $this->dettTabRows = []; $this->dettTabEdit = []; } if (in_array($this->tab, ['crediti', 'debiti'], true)) { $cd = $this->tab === 'crediti' ? 'C' : 'D'; [$this->creDebRows, $this->creDebEdit] = $this->loadCreDebPrecedCrudRows($codStabileLegacy, $this->legacyYear, $cd); // Liste documentali per sub-tab (FE/RA) $this->reloadDocumenti(); } else { $this->creDebRows = []; $this->creDebEdit = []; $this->feRows = []; $this->linkedFeIds = []; $this->raRows = []; $this->linkedRaIds = []; } $this->reloadDettaglio($codStabileLegacy, $this->legacyYear); } /** * Carica etichette user-friendly per le gestioni legacy (tabella "anni" in generale_stabile.mdb). * Ritorna una mappa: cartella (0001) => label (2024-25 o 2024). */ private function loadLegacyYearLabels(string $codStabileLegacy): array { if (! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) { return []; } $q = DB::connection('gescon_import')->table('gestioni_annuali') ->where('cod_stabile', $codStabileLegacy); $rows = $q->get(['cartella', 'anno_ordinario', 'anno_riscaldamento']); $out = []; foreach ($rows as $r) { $cartella = trim((string) ($r->cartella ?? '')); if ($cartella === '') { continue; } $label = trim((string) ($r->anno_riscaldamento ?? '')); if ($label === '') { $label = trim((string) ($r->anno_ordinario ?? '')); } if ($label === '') { $label = $cartella; } $out[$cartella] = $label; } return $out; } private function reloadDocumenti(): void { $this->feRows = []; $this->linkedFeIds = []; $this->raRows = []; $this->linkedRaIds = []; // Documenti FE/RA: normalmente solo sui DEBITI (fatture passive + ritenute da versare) if ($this->tab !== 'debiti') { return; } $stabile = $this->getActiveStabile(); $stabileId = $stabile?->id ? (int) $stabile->id : null; $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $stabileId) { return; } $cd = 'D'; $this->feRows = $this->loadFeRows($stabileId, $cd); $this->raRows = $this->loadRaRows($stabileId); if (! $this->selectedCreDebId || ! $codStabileLegacy || ! $this->legacyYear) { return; } $this->linkedFeIds = $this->loadLinkedFeIds($codStabileLegacy, $this->legacyYear, (int) $this->selectedCreDebId, $cd); $this->linkedRaIds = $this->loadLinkedRaIds($codStabileLegacy, $this->legacyYear, (int) $this->selectedCreDebId, $cd); } /** * @return array> */ private function loadFeRows(int $stabileId, string $cd): array { $cd = strtoupper(trim($cd)); $q = FatturaElettronica::query()->where('stabile_id', $stabileId); // Per i debiti ha senso mostrare prima le fatture da pagare. if ($cd === 'D') { $q->where('stato', '!=', 'pagata'); } // Situazione iniziale: mostriamo solo documenti antecedenti alla data bilancio (cut-off). // Esempio: bilancio al 01/01/2024 => fatture 2023. if (is_string($this->dataBilancio) && $this->dataBilancio !== '') { try { $cutoff = Carbon::parse($this->dataBilancio)->startOfDay(); $q->whereDate('data_fattura', '<', $cutoff->toDateString()); } catch (\Throwable) { // ignore } } $rows = $q ->orderBy('data_fattura', 'asc') ->orderBy('id', 'asc') ->limit(100) ->get(); return $rows->map(function (FatturaElettronica $f) { return [ 'id' => (int) $f->id, 'data_fattura' => $f->data_fattura?->format('Y-m-d'), 'data_scadenza' => $f->data_scadenza?->format('Y-m-d'), 'numero_fattura' => (string) ($f->numero_fattura ?? ''), 'fornitore_denominazione' => (string) ($f->fornitore_denominazione ?? ''), 'totale' => is_numeric($f->totale) ? (float) $f->totale : null, 'stato' => (string) ($f->stato ?? ''), ]; })->all(); } /** * @return array> */ private function loadRaRows(int $stabileId): array { $rows = RegistroRitenuteAcconto::query() ->with(['fornitore', 'gestione']) ->whereHas('gestione', function ($q) use ($stabileId) { $q->where('stabile_id', $stabileId); }) ->whereIn('stato_versamento', ['da_versare', 'compensata']) ->orderBy('data_competenza', 'desc') ->orderBy('id', 'desc') ->limit(100) ->get(); return $rows->map(function (RegistroRitenuteAcconto $r) { $fornitoreLabel = ''; try { $fornitoreLabel = (string) ($r->fornitore?->ragione_sociale ?? ''); } catch (\Throwable) { $fornitoreLabel = ''; } return [ 'id' => (int) $r->id, 'data_competenza' => $r->data_competenza?->format('Y-m-d'), 'numero_progressivo' => (int) ($r->numero_progressivo ?? 0), 'fornitore' => $fornitoreLabel, 'importo_ritenuta' => is_numeric($r->importo_ritenuta) ? (float) $r->importo_ritenuta : null, 'stato_versamento' => (string) ($r->stato_versamento ?? ''), ]; })->all(); } /** * @return array */ private function loadLinkedFeIds(string $codStabileLegacy, string $legacyYear, int $creDebPrecedId, string $cd): array { if (! Schema::hasTable('contabilita_situazione_iniziale_fe_links')) { return []; } return DB::table('contabilita_situazione_iniziale_fe_links') ->where('cod_stabile_legacy', $codStabileLegacy) ->where('legacy_year', $legacyYear) ->where('cre_deb_preced_id', $creDebPrecedId) ->where('c_d', strtoupper(trim($cd))) ->pluck('fattura_elettronica_id') ->map(fn ($v) => (int) $v) ->values() ->all(); } /** * @return array */ private function loadLinkedRaIds(string $codStabileLegacy, string $legacyYear, int $creDebPrecedId, string $cd): array { if (! Schema::hasTable('contabilita_situazione_iniziale_ra_links')) { return []; } return DB::table('contabilita_situazione_iniziale_ra_links') ->where('cod_stabile_legacy', $codStabileLegacy) ->where('legacy_year', $legacyYear) ->where('cre_deb_preced_id', $creDebPrecedId) ->where('c_d', strtoupper(trim($cd))) ->pluck('registro_ritenuta_acconto_id') ->map(fn ($v) => (int) $v) ->values() ->all(); } public function linkFatturaElettronica(int $fatturaId): void { $fatturaId = (int) $fatturaId; if ($fatturaId <= 0 || ! $this->selectedCreDebId || ! $this->legacyYear) { return; } if (! Schema::hasTable('contabilita_situazione_iniziale_fe_links')) { return; } $stabile = $this->getActiveStabile(); $stabileId = $stabile?->id ? (int) $stabile->id : null; $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } if ($this->tab !== 'debiti') { return; } $cd = 'D'; DB::table('contabilita_situazione_iniziale_fe_links')->updateOrInsert( [ 'cod_stabile_legacy' => $codStabileLegacy, 'legacy_year' => $this->legacyYear, 'cre_deb_preced_id' => (int) $this->selectedCreDebId, 'c_d' => $cd, 'fattura_elettronica_id' => $fatturaId, ], [ 'stabile_id' => $stabileId, 'updated_at' => now(), 'created_at' => now(), ] ); $this->reloadDocumenti(); } public function unlinkFatturaElettronica(int $fatturaId): void { $fatturaId = (int) $fatturaId; if ($fatturaId <= 0 || ! $this->selectedCreDebId || ! $this->legacyYear) { return; } if (! Schema::hasTable('contabilita_situazione_iniziale_fe_links')) { return; } $stabile = $this->getActiveStabile(); $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } if ($this->tab !== 'debiti') { return; } $cd = 'D'; DB::table('contabilita_situazione_iniziale_fe_links') ->where('cod_stabile_legacy', $codStabileLegacy) ->where('legacy_year', $this->legacyYear) ->where('cre_deb_preced_id', (int) $this->selectedCreDebId) ->where('c_d', $cd) ->where('fattura_elettronica_id', $fatturaId) ->delete(); $this->reloadDocumenti(); } public function linkRegistroRa(int $registroId): void { $registroId = (int) $registroId; if ($registroId <= 0 || ! $this->selectedCreDebId || ! $this->legacyYear) { return; } if (! Schema::hasTable('contabilita_situazione_iniziale_ra_links')) { return; } $stabile = $this->getActiveStabile(); $stabileId = $stabile?->id ? (int) $stabile->id : null; $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } if ($this->tab !== 'debiti') { return; } $cd = 'D'; DB::table('contabilita_situazione_iniziale_ra_links')->updateOrInsert( [ 'cod_stabile_legacy' => $codStabileLegacy, 'legacy_year' => $this->legacyYear, 'cre_deb_preced_id' => (int) $this->selectedCreDebId, 'c_d' => $cd, 'registro_ritenuta_acconto_id' => $registroId, ], [ 'stabile_id' => $stabileId, 'updated_at' => now(), 'created_at' => now(), ] ); $this->reloadDocumenti(); } public function unlinkRegistroRa(int $registroId): void { $registroId = (int) $registroId; if ($registroId <= 0 || ! $this->selectedCreDebId || ! $this->legacyYear) { return; } if (! Schema::hasTable('contabilita_situazione_iniziale_ra_links')) { return; } $stabile = $this->getActiveStabile(); $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } if ($this->tab !== 'debiti') { return; } $cd = 'D'; DB::table('contabilita_situazione_iniziale_ra_links') ->where('cod_stabile_legacy', $codStabileLegacy) ->where('legacy_year', $this->legacyYear) ->where('cre_deb_preced_id', (int) $this->selectedCreDebId) ->where('c_d', $cd) ->where('registro_ritenuta_acconto_id', $registroId) ->delete(); $this->reloadDocumenti(); } /** * @return array */ private function loadVocSpeOptions(?string $codStabileLegacy, ?string $legacyYear): array { if (! $codStabileLegacy) { return []; } if (! Schema::connection('gescon_import')->hasTable('voc_spe')) { return []; } $q = DB::connection('gescon_import')->table('voc_spe')->where('cod_stabile', $codStabileLegacy); if ($legacyYear && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $rows = $q ->orderBy('cod') ->limit(300) ->get(['cod', 'descriz']); $out = []; foreach ($rows as $r) { $cod = trim((string) ($r->cod ?? '')); if ($cod === '') { continue; } $label = trim((string) ($r->descriz ?? '')); $out[$cod] = $label !== '' ? ($cod . ' — ' . $label) : $cod; } return $out; } private function loadCreDebPrecedCrudRows(string $codStabileLegacy, ?string $legacyYear, string $cd): array { if (! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return [[], []]; } $cd = strtoupper(trim($cd)); if (! in_array($cd, ['C', 'D'], true)) { $cd = 'D'; } $q = DB::connection('gescon_import')->table('cre_deb_preced') ->whereRaw('UPPER(c_d) = ?', [$cd]); $hasForStabile = DB::connection('gescon_import')->table('cre_deb_preced') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $rows = $q->orderBy('cod_voc')->orderBy('id')->limit(5000) ->get(['id', 'c_d', 'cod_voc', 'des_voce', 'descrizione', 'importo_euro', 'n_stra', 'incluso']) ->map(fn ($r) => [ 'id' => (int) $r->id, 'c_d' => (string) ($r->c_d ?? ''), 'cod_voc' => (string) ($r->cod_voc ?? ''), 'des_voce' => (string) ($r->des_voce ?? ''), 'descrizione' => (string) ($r->descrizione ?? ''), 'importo_euro' => (float) ($r->importo_euro ?? 0.0), 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, 'incluso' => $r->incluso !== null ? (int) $r->incluso : 1, ])->all(); // Lookup descrizione voce di spesa da voc_spe (stesso MDB): cod_voc -> voc_spe.cod $vocMap = []; if (! empty($rows) && Schema::connection('gescon_import')->hasTable('voc_spe')) { $cods = array_values(array_unique(array_filter(array_map(fn ($r) => trim((string) ($r['cod_voc'] ?? '')), $rows), fn ($v) => $v !== ''))); if (! empty($cods)) { $vq = DB::connection('gescon_import')->table('voc_spe')->where('cod_stabile', $codStabileLegacy); if ($legacyYear && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) { $vq->where('legacy_year', $legacyYear); } if (Schema::connection('gescon_import')->hasColumn('voc_spe', 'cod')) { $vq->whereIn('cod', $cods); } foreach ($vq->get(['cod', 'descriz', 'consuntivo_euro', 'preventivo_euro']) as $vr) { $k = trim((string) ($vr->cod ?? '')); if ($k === '') { continue; } $vocMap[$k] = [ 'descriz' => trim((string) ($vr->descriz ?? '')), 'consuntivo_euro' => $vr->consuntivo_euro !== null ? (float) $vr->consuntivo_euro : null, 'preventivo_euro' => $vr->preventivo_euro !== null ? (float) $vr->preventivo_euro : null, ]; } } } if (! empty($vocMap)) { foreach ($rows as &$row) { $cod = trim((string) ($row['cod_voc'] ?? '')); if ($cod !== '' && isset($vocMap[$cod])) { $row['voc_spe_descriz'] = (string) ($vocMap[$cod]['descriz'] ?? ''); $row['voc_spe_consuntivo_euro'] = $vocMap[$cod]['consuntivo_euro'] ?? null; $row['voc_spe_preventivo_euro'] = $vocMap[$cod]['preventivo_euro'] ?? null; } else { $row['voc_spe_descriz'] = ''; $row['voc_spe_consuntivo_euro'] = null; $row['voc_spe_preventivo_euro'] = null; } } unset($row); } else { foreach ($rows as &$row) { $row['voc_spe_descriz'] = ''; $row['voc_spe_consuntivo_euro'] = null; $row['voc_spe_preventivo_euro'] = null; } unset($row); } $edit = []; foreach ($rows as $row) { $id = (int) ($row['id'] ?? 0); if ($id <= 0) { continue; } $edit[$id] = [ 'cod_voc' => $row['cod_voc'], 'des_voce' => $row['des_voce'], 'descrizione' => $row['descrizione'], 'importo_euro' => $row['importo_euro'], 'n_stra' => $row['n_stra'], 'incluso' => $row['incluso'], ]; } return [$rows, $edit]; } private function loadDettTabCrudRows(string $codStabileLegacy, ?string $legacyYear): array { if (! Schema::connection('gescon_import')->hasTable('dett_tab')) { return [[], []]; } $q = DB::connection('gescon_import')->table('dett_tab') ->where('cod_tab', 'like', 'CONG.%'); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('dett_tab') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $labels = $this->loadCondominiLabels($codStabileLegacy, $legacyYear); $rows = $q->orderBy('cod_tab') ->orderBy('n_stra') ->orderByRaw('CAST(id_cond AS UNSIGNED)') ->orderBy('id') ->limit(5000) ->get(['id', 'cod_tab', 'id_cond', 'cond_inquil', 'mm', 'cons_euro', 'n_stra', 'unico']) ->map(fn ($r) => [ 'id' => (int) $r->id, 'cod_tab' => (string) ($r->cod_tab ?? ''), 'id_cond' => (string) ($r->id_cond ?? ''), 'cond_label' => (string) ($labels[trim((string) ($r->id_cond ?? ''))] ?? ''), 'cond_inquil' => (string) ($r->cond_inquil ?? ''), 'mm' => $r->mm !== null ? (float) $r->mm : null, 'cons_euro' => $r->cons_euro !== null ? (float) $r->cons_euro : 0.0, 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, 'unico' => $r->unico !== null ? (int) $r->unico : 0, ])->all(); $edit = []; foreach ($rows as $row) { $id = (int) ($row['id'] ?? 0); if ($id <= 0) { continue; } $edit[$id] = [ 'cod_tab' => $row['cod_tab'], 'id_cond' => $row['id_cond'], 'cond_inquil' => $row['cond_inquil'], 'mm' => $row['mm'], 'cons_euro' => $row['cons_euro'], 'n_stra' => $row['n_stra'], 'unico' => $row['unico'], ]; } return [$rows, $edit]; } public function saveCreDebRow(int $id): void { if ($id <= 0 || ! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return; } $edit = $this->creDebEdit[$id] ?? null; if (! is_array($edit)) { return; } $importo = $this->parseMoney(is_string($edit['importo_euro'] ?? null) ? (string) $edit['importo_euro'] : null); if ($importo === null && is_numeric($edit['importo_euro'] ?? null)) { $importo = (float) $edit['importo_euro']; } if ($importo === null) { $importo = 0.0; } DB::connection('gescon_import')->table('cre_deb_preced') ->where('id', $id) ->update([ 'cod_voc' => is_string($edit['cod_voc'] ?? null) ? trim((string) $edit['cod_voc']) : null, 'des_voce' => is_string($edit['des_voce'] ?? null) ? trim((string) $edit['des_voce']) : null, 'descrizione' => is_string($edit['descrizione'] ?? null) ? trim((string) $edit['descrizione']) : null, 'importo_euro' => round((float) $importo, 2), 'n_stra' => is_numeric($edit['n_stra'] ?? null) ? (int) $edit['n_stra'] : null, 'incluso' => ! empty($edit['incluso']) ? 1 : 0, 'updated_at' => now(), ]); $this->reloadData(); } public function deleteCreDebRow(int $id): void { if ($id <= 0 || ! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return; } DB::connection('gescon_import')->table('cre_deb_preced')->where('id', $id)->delete(); unset($this->creDebEdit[$id]); $this->reloadData(); } public function addCreDebRow(string $cd): void { if (! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return; } $stabile = $this->getActiveStabile(); $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } $cd = strtoupper(trim($cd)); if (! in_array($cd, ['C', 'D'], true)) { $cd = 'D'; } $importo = $this->parseMoney(is_string($this->formCreDebAdd['importo_euro'] ?? null) ? (string) $this->formCreDebAdd['importo_euro'] : null); if ($importo === null) { return; } $row = [ 'cod_stabile' => $codStabileLegacy, 'legacy_year' => $this->legacyYear, 'c_d' => $cd, 'cod_voc' => is_string($this->formCreDebAdd['cod_voc'] ?? null) ? trim((string) $this->formCreDebAdd['cod_voc']) : null, 'des_voce' => is_string($this->formCreDebAdd['des_voce'] ?? null) ? trim((string) $this->formCreDebAdd['des_voce']) : null, 'descrizione' => is_string($this->formCreDebAdd['descrizione'] ?? null) ? trim((string) $this->formCreDebAdd['descrizione']) : null, 'importo_euro' => round((float) $importo, 2), 'n_stra' => is_numeric($this->formCreDebAdd['n_stra'] ?? null) ? (int) $this->formCreDebAdd['n_stra'] : null, 'incluso' => ! empty($this->formCreDebAdd['incluso']) ? 1 : 0, 'created_at' => now(), 'updated_at' => now(), ]; $this->editingCreDebId = null; DB::connection('gescon_import')->table('cre_deb_preced')->insert($row); $this->formCreDebAdd['cod_voc'] = null; $this->selectedCreDebId = null; $this->formCreDebAdd['des_voce'] = null; $this->formCreDebAdd['descrizione'] = null; $this->formCreDebAdd['importo_euro'] = null; $this->formCreDebAdd['n_stra'] = null; $this->formCreDebAdd['incluso'] = 1; $this->reloadData(); } public function saveDettTabRow(int $id): void { if ($id <= 0 || ! Schema::connection('gescon_import')->hasTable('dett_tab')) { return; } $edit = $this->dettTabEdit[$id] ?? null; if (! is_array($edit)) { return; } $cons = $this->parseMoney(is_string($edit['cons_euro'] ?? null) ? (string) $edit['cons_euro'] : null); if ($cons === null && is_numeric($edit['cons_euro'] ?? null)) { $cons = (float) $edit['cons_euro']; } if ($cons === null) { $cons = 0.0; } DB::connection('gescon_import')->table('dett_tab') ->where('id', $id) ->update([ 'cod_tab' => is_string($edit['cod_tab'] ?? null) ? trim((string) $edit['cod_tab']) : null, 'id_cond' => is_string($edit['id_cond'] ?? null) ? trim((string) $edit['id_cond']) : null, 'cond_inquil' => is_string($edit['cond_inquil'] ?? null) ? trim((string) $edit['cond_inquil']) : null, 'mm' => is_numeric($edit['mm'] ?? null) ? (float) $edit['mm'] : null, 'cons_euro' => round((float) $cons, 2), 'n_stra' => is_numeric($edit['n_stra'] ?? null) ? (int) $edit['n_stra'] : null, 'unico' => ! empty($edit['unico']) ? 1 : 0, 'updated_at' => now(), ]); $this->reloadData(); } public function deleteDettTabRow(int $id): void { if ($id <= 0 || ! Schema::connection('gescon_import')->hasTable('dett_tab')) { return; } DB::connection('gescon_import')->table('dett_tab')->where('id', $id)->delete(); unset($this->dettTabEdit[$id]); $this->reloadData(); } public function addDettTabRow(): void { if (! Schema::connection('gescon_import')->hasTable('dett_tab')) { return; } $stabile = $this->getActiveStabile(); $codStabileLegacy = $stabile?->codice_stabile ? (string) $stabile->codice_stabile : null; if (! $codStabileLegacy) { return; } $codTab = is_string($this->formDettTabAdd['cod_tab'] ?? null) ? trim((string) $this->formDettTabAdd['cod_tab']) : ''; $idCond = is_string($this->formDettTabAdd['id_cond'] ?? null) ? trim((string) $this->formDettTabAdd['id_cond']) : ''; if ($codTab === '' || $idCond === '') { return; } $cons = $this->parseMoney(is_string($this->formDettTabAdd['cons_euro'] ?? null) ? (string) $this->formDettTabAdd['cons_euro'] : null); if ($cons === null) { return; } $row = [ 'cod_tab' => $codTab, 'id_cond' => $idCond, 'cond_inquil' => is_string($this->formDettTabAdd['cond_inquil'] ?? null) ? trim((string) $this->formDettTabAdd['cond_inquil']) : null, 'mm' => is_numeric($this->formDettTabAdd['mm'] ?? null) ? (float) $this->formDettTabAdd['mm'] : null, 'cons_euro' => round((float) $cons, 2), 'n_stra' => is_numeric($this->formDettTabAdd['n_stra'] ?? null) ? (int) $this->formDettTabAdd['n_stra'] : null, 'unico' => ! empty($this->formDettTabAdd['unico']) ? 1 : 0, 'created_at' => now(), 'updated_at' => now(), ]; $this->editingDettTabId = null; if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $row['cod_stabile'] = $codStabileLegacy; } if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $row['legacy_year'] = $this->legacyYear; } DB::connection('gescon_import')->table('dett_tab')->insert($row); $this->formDettTabAdd['id_cond'] = null; $this->formDettTabAdd['cond_inquil'] = null; $this->formDettTabAdd['mm'] = null; $this->formDettTabAdd['cons_euro'] = null; $this->formDettTabAdd['n_stra'] = null; $this->formDettTabAdd['unico'] = 0; $this->reloadData(); } private function loadLegacyYears(?string $codStabileLegacy): array { $years = []; // Preferisci la mappa "anni" (generale_stabile.mdb) per ottenere l'elenco cartelle in ordine naturale. if ($codStabileLegacy && Schema::connection('gescon_import')->hasTable('gestioni_annuali')) { $rows = DB::connection('gescon_import')->table('gestioni_annuali') ->where('cod_stabile', $codStabileLegacy) ->get(['cartella', 'anno_ordinario', 'anno_riscaldamento']); $mapped = []; foreach ($rows as $r) { $cartella = trim((string) ($r->cartella ?? '')); if ($cartella === '') { continue; } $ordYear = null; $ao = trim((string) ($r->anno_ordinario ?? '')); if ($ao !== '' && preg_match('/^(\d{4})$/', $ao, $m)) { $ordYear = (int) $m[1]; } $mapped[] = ['cartella' => $cartella, 'ord' => $ordYear ?? 0]; } usort($mapped, function (array $a, array $b): int { if ($a['ord'] !== $b['ord']) { return $a['ord'] <=> $b['ord']; } return strcmp((string) $a['cartella'], (string) $b['cartella']); }); foreach ($mapped as $m) { $years[] = (string) $m['cartella']; } } if ($codStabileLegacy && Schema::connection('gescon_import')->hasTable('cre_deb_preced') && Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'legacy_year')) { $years = array_merge($years, DB::connection('gescon_import')->table('cre_deb_preced') ->where('cod_stabile', $codStabileLegacy) ->whereNotNull('legacy_year') ->distinct() ->orderBy('legacy_year') ->pluck('legacy_year') ->map(fn ($v) => (string) $v) ->all()); } if ($codStabileLegacy && Schema::connection('gescon_import')->hasTable('dett_tab') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $q = DB::connection('gescon_import')->table('dett_tab') ->where('cod_tab', 'like', 'CONG.%') ->whereNotNull('legacy_year'); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $q->where('cod_stabile', $codStabileLegacy); } $years = array_merge($years, $q->distinct()->orderBy('legacy_year')->pluck('legacy_year')->map(fn ($v) => (string) $v)->all()); } // Include anni presenti nelle straordinarie anche se non ci sono righe in cre_deb_preced/dett_tab. if (Schema::connection('gescon_import')->hasTable('straordinarie') && Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year')) { $q = DB::connection('gescon_import')->table('straordinarie') ->whereNotNull('legacy_year'); if ($codStabileLegacy && Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('straordinarie') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where(function ($q) use ($codStabileLegacy) { $q->where('cod_stabile', $codStabileLegacy) ->orWhereNull('cod_stabile') ->orWhere('cod_stabile', ''); }); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } $years = array_merge($years, $q->distinct()->orderBy('legacy_year')->pluck('legacy_year')->map(fn ($v) => (string) $v)->all()); } $years = array_values(array_unique(array_filter($years, fn ($v) => $v !== ''))); // Se non abbiamo una mappa "anni", ordina in modo consistente. if (! ($codStabileLegacy && Schema::connection('gescon_import')->hasTable('gestioni_annuali'))) { sort($years); } return $years; } private function emptyBilancio(): array { return [ 'ordinaria' => ['cong_pos' => 0.0, 'cong_neg' => 0.0, 'crediti' => 0.0, 'debiti' => 0.0, 'attivita' => 0.0, 'passivita' => 0.0], 'riscaldamento' => ['cong_pos' => 0.0, 'cong_neg' => 0.0, 'crediti' => 0.0, 'debiti' => 0.0, 'attivita' => 0.0, 'passivita' => 0.0], 'straordinarie' => [], 'tot_attivita' => 0.0, 'tot_passivita' => 0.0, 'finanziario' => 0.0, 'banca' => [ 'saldo_manuale' => null, 'saldo_letto' => null, 'delta_manuale' => null, 'delta_letto' => null, ], ]; } private function defaultDataBilancio(): string { $legacyYear = $this->legacyYear; if ($legacyYear && preg_match('/^\d{4}$/', $legacyYear)) { $n = (int) $legacyYear; // Se è già un anno reale (es. 2024), usalo direttamente. if ($n >= 1900 && $n <= 2100) { return sprintf('%04d-12-31', $n - 1); } // Altrimenti tratta 0001/0002/... come indice esercizio (0001 = 2024). if ($n >= 1 && $n <= 99) { $year = 2022 + $n; return sprintf('%04d-12-31', $year); } } return now()->toDateString(); } private function loadContiOptions(int $stabileId): array { if (! DB::getSchemaBuilder()->hasTable('dati_bancari')) { return []; } return DatiBancari::query() ->where('stabile_id', $stabileId) ->orderBy('id') ->get(['id', 'denominazione_banca', 'iban']) ->mapWithKeys(function (DatiBancari $c): array { $iban = is_string($c->iban) ? trim((string) $c->iban) : ''; $label = trim((string) ($c->denominazione_banca ?? '')); $label = $label !== '' ? ($label . ($iban !== '' ? (' · ' . $iban) : '')) : ($iban !== '' ? $iban : ('Conto #' . $c->id)); return [(string) $c->id => $label]; }) ->all(); } private function computeBilancio( int $stabileId, string $codStabileLegacy, ?string $legacyYear, string $dataBilancio, ?string $saldoBancaManuale, array $risorseFinanziarie, ): array { $ordinaria = $this->computeBucketOrdinaria($codStabileLegacy, $legacyYear); $riscaldamento = $this->computeBucketRiscaldamento($codStabileLegacy, $legacyYear); // Straordinarie: cross-year. Identità = (legacy_year, id_stra). $straEntries = $this->loadStraEntries($codStabileLegacy, $legacyYear); $stra = []; foreach ($straEntries as $e) { $eYear = $e['legacy_year'] ?? null; $eStra = (int) ($e['n_stra'] ?? 0); $eKey = (string) ($e['key'] ?? (string) $eStra); if ($eStra <= 0) { continue; } $bucket = $this->computeBucketStraordinaria($codStabileLegacy, $eYear ?? $legacyYear, $eStra); $bucket['legacy_year'] = $eYear; $bucket['key'] = $eKey; $bucket['label'] = $this->formatStraLabel($eYear, $eStra, (string) ($bucket['titolo'] ?? '')); $stra[$eKey] = $bucket; } // Totali generali (stabile): CREDITI/DEBITI conteggiati UNA SOLA VOLTA, // mentre i conguagli si sommano per gestione (ORD/RISC/STRA). $totCreditiUnici = (float) ($ordinaria['crediti'] ?? 0.0); $totDebitiUnici = (float) ($ordinaria['debiti'] ?? 0.0); $totCongPos = (float) ($ordinaria['cong_pos'] ?? 0.0) + (float) ($riscaldamento['cong_pos'] ?? 0.0); $totCongNeg = (float) ($ordinaria['cong_neg'] ?? 0.0) + (float) ($riscaldamento['cong_neg'] ?? 0.0); $totAtt = $totCreditiUnici + $totCongPos; $totPas = $totDebitiUnici + $totCongNeg; foreach ($stra as $row) { $totAtt += (float) ($row['cong_pos'] ?? 0.0) + (float) ($row['crediti'] ?? 0.0); $totPas += (float) ($row['cong_neg'] ?? 0.0) + (float) ($row['debiti'] ?? 0.0); } $totAtt = round($totAtt, 2); $totPas = round($totPas, 2); $fin = round($totAtt - $totPas, 2); $saldoLetto = $this->computeSaldoBancaTotaleLetto($risorseFinanziarie); $saldoManualeVal = $this->parseMoney($saldoBancaManuale); return [ 'ordinaria' => $ordinaria, 'riscaldamento' => $riscaldamento, 'straordinarie' => $stra, 'tot_attivita' => $totAtt, 'tot_passivita' => $totPas, 'finanziario' => $fin, 'banca' => [ 'saldo_manuale' => $saldoManualeVal, 'saldo_letto' => $saldoLetto, 'delta_manuale' => $saldoManualeVal !== null ? round($saldoManualeVal - $fin, 2) : null, 'delta_letto' => $saldoLetto !== null ? round($saldoLetto - $fin, 2) : null, ], ]; } private function computeBucketOrdinaria(string $codStabileLegacy, ?string $legacyYear): array { $crediti = $this->sumCreDebPreced($codStabileLegacy, $legacyYear, 'C', 'non_stra') + $this->sumManualVoci('ordinaria', 'crediti', null, $legacyYear); $debiti = $this->sumCreDebPreced($codStabileLegacy, $legacyYear, 'D', 'non_stra') + $this->sumManualVoci('ordinaria', 'debiti', null, $legacyYear); [$congPos, $congNeg] = $this->sumConguagli($codStabileLegacy, $legacyYear, 'CONG.O', 'non_stra'); [$mPos, $mNeg] = $this->sumManualConguagli($legacyYear, 'CONG.O', 'non_stra'); $congPos += $mPos; $congNeg += $mNeg; $att = round($crediti + $congPos, 2); $pas = round($debiti + $congNeg, 2); return [ 'cong_pos' => $congPos, 'cong_neg' => $congNeg, 'crediti' => $crediti, 'debiti' => $debiti, 'attivita' => $att, 'passivita' => $pas, ]; } private function computeBucketRiscaldamento(string $codStabileLegacy, ?string $legacyYear): array { // IMPORTANTE: // `cre_deb_preced` (crediti/debiti pregressi) non distingue la gestione (ordinaria vs riscaldamento). // Per evitare duplicazioni percepite in UI, i Crediti/Debiti "pregressi" vengono mostrati SOLO in Ordinaria. // Qui teniamo esclusivamente i conguagli CONG.R (e gli eventuali manuali riscaldamento, se presenti). $crediti = 0.0; $debiti = 0.0; [$congPos, $congNeg] = $this->sumConguagli($codStabileLegacy, $legacyYear, 'CONG.R', 'non_stra'); [$mPos, $mNeg] = $this->sumManualConguagli($legacyYear, 'CONG.R', 'non_stra'); $congPos += $mPos; $congNeg += $mNeg; $att = round($crediti + $congPos, 2); $pas = round($debiti + $congNeg, 2); return [ 'cong_pos' => $congPos, 'cong_neg' => $congNeg, 'crediti' => $crediti, 'debiti' => $debiti, 'attivita' => $att, 'passivita' => $pas, ]; } private function computeBucketStraordinaria(string $codStabileLegacy, ?string $legacyYear, int $nStra): array { $crediti = $this->sumCreDebPreced($codStabileLegacy, $legacyYear, 'C', $nStra) + $this->sumManualVoci('straordinaria', 'crediti', $nStra, $legacyYear); $debiti = $this->sumCreDebPreced($codStabileLegacy, $legacyYear, 'D', $nStra) + $this->sumManualVoci('straordinaria', 'debiti', $nStra, $legacyYear); [$congPos, $congNeg] = $this->sumConguagli($codStabileLegacy, $legacyYear, null, $nStra); [$mPos, $mNeg] = $this->sumManualConguagli($legacyYear, null, $nStra); $congPos += $mPos; $congNeg += $mNeg; $titolo = $this->resolveStraordinariaTitolo($codStabileLegacy, $legacyYear, $nStra); $att = round($crediti + $congPos, 2); $pas = round($debiti + $congNeg, 2); return [ 'n_stra' => $nStra, 'titolo' => $titolo, 'cong_pos' => $congPos, 'cong_neg' => $congNeg, 'crediti' => $crediti, 'debiti' => $debiti, 'attivita' => $att, 'passivita' => $pas, ]; } private function formatStraLabel(?string $legacyYear, int $nStra, string $titolo): string { $titolo = trim($titolo); $prefix = ''; if ($legacyYear) { $label = $this->legacyYearLabels[$legacyYear] ?? null; if (is_string($label) && trim($label) !== '') { $prefix = trim($label) . ' · '; } } $base = 'Straordinaria #' . $nStra; if ($titolo === '') { return $prefix . $base; } return $prefix . $base . ' · ' . $titolo; } private function legacyYearToRealYear(?string $legacyYear): ?int { if (! $legacyYear || ! preg_match('/^\d{4}$/', $legacyYear)) { return null; } $n = (int) $legacyYear; if ($n >= 1900 && $n <= 2100) { return $n; } return null; } private function sumManualVoci(string $gestione, string $categoria, ?int $nStra, ?string $legacyYear): float { if (! DB::getSchemaBuilder()->hasTable('contabilita_situazione_iniziale_voci')) { return 0.0; } $stabileId = $this->getActiveStabile()?->id; if (! $stabileId) { return 0.0; } $q = DB::table('contabilita_situazione_iniziale_voci') ->where('stabile_id', (int) $stabileId) ->where('gestione', $gestione) ->where('categoria', $categoria) ->where('incluso', 1); if ($legacyYear !== null && $legacyYear !== '') { $q->where('legacy_year', $legacyYear); } if ($gestione === 'straordinaria') { $q->where('n_stra', $nStra); } else { $q->whereNull('n_stra'); } return round((float) ($q->sum('importo_euro') ?? 0.0), 2); } private function sumManualConguagli(?string $legacyYear, ?string $codTab, int|string $straFilter): array { if (! DB::getSchemaBuilder()->hasTable('contabilita_situazione_iniziale_conguagli')) { return [0.0, 0.0]; } $stabileId = $this->getActiveStabile()?->id; if (! $stabileId) { return [0.0, 0.0]; } $q = DB::table('contabilita_situazione_iniziale_conguagli') ->where('stabile_id', (int) $stabileId) ->where('incluso', 1); if ($legacyYear !== null && $legacyYear !== '') { $q->where('legacy_year', $legacyYear); } if ($codTab) { $q->where('cod_tab', $codTab); } if ($straFilter === 'non_stra') { $q->whereNull('n_stra'); } elseif (is_int($straFilter) && $straFilter > 0) { $q->where('n_stra', $straFilter); } $pos = (float) (clone $q)->where('cons_euro', '>', 0)->sum('cons_euro'); $neg = (float) (clone $q)->where('cons_euro', '<', 0)->sum('cons_euro'); return [round($pos, 2), round(abs($neg), 2)]; } private function resolveStraordinariaTitolo(string $codStabileLegacy, ?string $legacyYear, int $nStra): string { if (! Schema::connection('gescon_import')->hasTable('straordinarie')) { return 'Straordinaria #' . $nStra; } $row = null; $applyCodStabileFallback = function ($q) use ($codStabileLegacy) { if (! Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) { return $q; } $hasForStabile = DB::connection('gescon_import')->table('straordinarie') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { // Include anche righe con cod_stabile vuoto: alcuni import parziali le lasciano NULL/''. return $q->where(function ($q) use ($codStabileLegacy) { $q->where('cod_stabile', $codStabileLegacy) ->orWhereNull('cod_stabile') ->orWhere('cod_stabile', ''); }); } return $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); }; // Preferisci l'anno selezionato se presente (per compatibilità con import per-cartella), // ma fai fallback cross-year così la UI può mostrare SEMPRE tutte le straordinarie. if ($legacyYear && Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year')) { $q = DB::connection('gescon_import')->table('straordinarie'); $q = $applyCodStabileFallback($q); $row = $q ->where('id_stra', $nStra) ->where('legacy_year', $legacyYear) ->first(['codice', 'descriz_prev_cons', 'descriz_ricev']); } if (! $row) { $q = DB::connection('gescon_import')->table('straordinarie'); $q = $applyCodStabileFallback($q); $row = $q ->where('id_stra', $nStra) ->orderByDesc(Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year') ? 'legacy_year' : 'id_stra') ->first(['codice', 'descriz_prev_cons', 'descriz_ricev']); } $title = ''; if ($row) { $title = trim((string) ($row->descriz_prev_cons ?? '')); if ($title === '') { $title = trim((string) ($row->descriz_ricev ?? '')); } if ($title === '') { $title = trim((string) ($row->codice ?? '')); } } return $title !== '' ? $title : ('Straordinaria #' . $nStra); } /** * @return array */ private function loadStraEntries(string $codStabileLegacy, ?string $legacyYear): array { $entries = []; $add = function (?string $y, int $n) use (&$entries) { if ($n <= 0) { return; } $yy = $y !== null && $y !== '' ? (string) $y : null; $key = $yy ? ($yy . ':' . $n) : (string) $n; $entries[$key] = ['key' => $key, 'legacy_year' => $yy, 'n_stra' => $n]; }; if (Schema::connection('gescon_import')->hasTable('cre_deb_preced') && Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'n_stra')) { $q = DB::connection('gescon_import')->table('cre_deb_preced') ->whereNotNull('n_stra') ->where('n_stra', '>', 0); // Include anche righe con cod_stabile vuoto quando esistono righe per lo stabile. $hasForStabile = DB::connection('gescon_import')->table('cre_deb_preced') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where(function ($q) use ($codStabileLegacy) { $q->where('cod_stabile', $codStabileLegacy) ->orWhereNull('cod_stabile') ->orWhere('cod_stabile', ''); }); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } if (Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'incluso')) { $q->where('incluso', 1); } // IMPORTANTE: NON filtrare per legacy_year: vogliamo vedere tutte le straordinarie cross-year. if (Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'legacy_year')) { foreach ($q->distinct()->get(['legacy_year', 'n_stra']) as $r) { $add(is_string($r->legacy_year ?? null) ? (string) $r->legacy_year : null, (int) ($r->n_stra ?? 0)); } } else { foreach ($q->distinct()->orderBy('n_stra')->pluck('n_stra') as $v) { $add($legacyYear, (int) $v); } } } if (Schema::connection('gescon_import')->hasTable('dett_tab') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'n_stra')) { $q = DB::connection('gescon_import')->table('dett_tab') ->where('cod_tab', 'like', 'CONG.%') ->whereNotNull('n_stra') ->where('n_stra', '>', 0); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('dett_tab') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where(function ($q) use ($codStabileLegacy) { $q->where('cod_stabile', $codStabileLegacy) ->orWhereNull('cod_stabile') ->orWhere('cod_stabile', ''); }); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } // IMPORTANTE: NON filtrare per legacy_year: vogliamo vedere tutte le straordinarie cross-year. if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { foreach ($q->distinct()->get(['legacy_year', 'n_stra']) as $r) { $add(is_string($r->legacy_year ?? null) ? (string) $r->legacy_year : null, (int) ($r->n_stra ?? 0)); } } else { foreach ($q->distinct()->orderBy('n_stra')->pluck('n_stra') as $v) { $add($legacyYear, (int) $v); } } } // Include tutte le straordinarie dichiarate nel MDB (tabella gescon_import.straordinarie), anche se importi = 0. if (Schema::connection('gescon_import')->hasTable('straordinarie') && Schema::connection('gescon_import')->hasColumn('straordinarie', 'id_stra')) { $q = DB::connection('gescon_import')->table('straordinarie') ->whereNotNull('id_stra') ->where('id_stra', '>', 0); // Fallback: alcuni import da singolo_anno.mdb non popolano cod_stabile. if (Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('straordinarie') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where(function ($q) use ($codStabileLegacy) { $q->where('cod_stabile', $codStabileLegacy) ->orWhereNull('cod_stabile') ->orWhere('cod_stabile', ''); }); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } // IMPORTANTE: NON filtrare per legacy_year. // Le straordinarie sono "tabelle" trasversali: vogliamo vederle tutte (es. 4 in 0001 + 5 in 0003 = 9). if (Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year')) { foreach ($q->distinct()->get(['legacy_year', 'id_stra']) as $r) { $add(is_string($r->legacy_year ?? null) ? (string) $r->legacy_year : null, (int) ($r->id_stra ?? 0)); } } else { foreach ($q->distinct()->orderBy('id_stra')->pluck('id_stra') as $v) { $add($legacyYear, (int) $v); } } } $list = array_values($entries); usort($list, function (array $a, array $b): int { $ya = (string) ($a['legacy_year'] ?? ''); $yb = (string) ($b['legacy_year'] ?? ''); if ($ya !== $yb) { return $ya <=> $yb; } return ((int) $a['n_stra']) <=> ((int) $b['n_stra']); }); return $list; } private function sumCreDebPreced(string $codStabileLegacy, ?string $legacyYear, string $cd, int|string $straFilter): float { if (! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return 0.0; } $q = DB::connection('gescon_import')->table('cre_deb_preced') ->whereRaw('UPPER(c_d) = ?', [strtoupper($cd)]); // Fallback: alcuni import da singolo_anno.mdb non popolano cod_stabile. // In quel caso, se non esistono righe per lo stabile attivo, usa le righe con cod_stabile NULL/vuoto. $hasForStabile = DB::connection('gescon_import')->table('cre_deb_preced') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } if (Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'incluso')) { $q->where('incluso', 1); } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } if ($straFilter === 'non_stra') { $q->where(function ($q) { $q->whereNull('n_stra')->orWhere('n_stra', '<=', 0); }); } elseif (is_int($straFilter) && $straFilter > 0) { $q->where('n_stra', $straFilter); } return round((float) ($q->sum('importo_euro') ?? 0.0), 2); } private function sumConguagli(string $codStabileLegacy, ?string $legacyYear, ?string $codTab, int|string $straFilter): array { if (! Schema::connection('gescon_import')->hasTable('dett_tab')) { return [0.0, 0.0]; } $q = DB::connection('gescon_import')->table('dett_tab'); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('dett_tab') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } if ($codTab) { $q->where('cod_tab', $codTab); } if ($straFilter === 'non_stra') { $q->where(function ($q) { $q->whereNull('n_stra')->orWhere('n_stra', '<=', 0); }); } elseif (is_int($straFilter) && $straFilter > 0) { $q->where('n_stra', $straFilter); } $pos = (float) (clone $q)->where('cons_euro', '>', 0)->sum('cons_euro'); $neg = (float) (clone $q)->where('cons_euro', '<', 0)->sum('cons_euro'); return [round($pos, 2), round(abs($neg), 2)]; } private function computeRisorseFinanziarie(int $stabileId, string $date): array { if (! DB::getSchemaBuilder()->hasTable('contabilita_movimenti_banca')) { return []; } if (! DB::getSchemaBuilder()->hasTable('dati_bancari')) { return []; } $conti = DatiBancari::query() ->where('stabile_id', $stabileId) ->whereNotNull('iban') ->where('iban', '!=', '') ->orderBy('id') ->get(['id', 'denominazione_banca', 'iban', 'saldo_iniziale']); if ($conti->isEmpty()) { return []; } $dateC = Carbon::parse($date); $out = []; foreach ($conti as $conto) { $iban = is_string($conto->iban) ? trim((string) $conto->iban) : ''; if ($iban === '') { continue; } $saldoIniziale = is_numeric($conto->saldo_iniziale) ? (float) $conto->saldo_iniziale : 0.0; $sumImporti = (float) (MovimentoBanca::query() ->where('stabile_id', $stabileId) ->where('conto_id', (int) $conto->id) ->where('iban', $iban) ->whereDate('data', '<=', $dateC->toDateString()) ->sum('importo') ?? 0.0); $offset = $this->resolveSaldoOffsetDaSaldi($stabileId, (int) $conto->id, $iban, $dateC); $saldo = round($saldoIniziale + $sumImporti + $offset, 2); $label = trim((string) ($conto->denominazione_banca ?? '')); $label = $label !== '' ? ($label . ' · ' . $iban) : $iban; $out[] = [ 'conto_id' => (int) $conto->id, 'iban' => $iban, 'label' => $label, 'saldo' => $saldo, ]; } return $out; } private function computeSaldoBancaTotaleLetto(array $risorseFinanziarie): ?float { if ($risorseFinanziarie === []) { return null; } $tot = 0.0; foreach ($risorseFinanziarie as $r) { $tot += (float) ($r['saldo'] ?? 0.0); } return round($tot, 2); } private function resolveSaldoOffsetDaSaldi(int $stabileId, int $contoId, string $iban, Carbon $date): float { if (! DB::getSchemaBuilder()->hasTable('contabilita_saldi_conti')) { return 0.0; } $match = SaldoConto::query() ->where('stabile_id', $stabileId) ->where('conto_id', $contoId) ->where('iban', $iban) ->whereDate('data_saldo', '<=', $date->toDateString()) ->orderByDesc('data_saldo') ->orderByDesc('id') ->first(['id', 'data_saldo', 'saldo']); if (! $match) { return 0.0; } $saldoIniziale = $this->resolveSaldoInizialeDaDatiBancari($stabileId, $contoId, $iban); $sumToDateSaldo = (float) (MovimentoBanca::query() ->where('stabile_id', $stabileId) ->where('conto_id', $contoId) ->where('iban', $iban) ->whereDate('data', '<=', $match->data_saldo->toDateString()) ->sum('importo') ?? 0.0); $saldoAttesoAllaDataSaldo = is_numeric($match->saldo) ? (float) $match->saldo : 0.0; $saldoCalcolatoAllaDataSaldo = $saldoIniziale + $sumToDateSaldo; return (float) ($saldoAttesoAllaDataSaldo - $saldoCalcolatoAllaDataSaldo); } private function resolveSaldoInizialeDaDatiBancari(int $stabileId, int $contoId, string $iban): float { $row = DatiBancari::query() ->where('stabile_id', $stabileId) ->where('id', $contoId) ->where('iban', $iban) ->first(['saldo_iniziale']); return $row && is_numeric($row->saldo_iniziale) ? (float) $row->saldo_iniziale : 0.0; } private function parseMoney(?string $value): ?float { if (! is_string($value)) { return null; } $v = trim($value); if ($v === '') { return null; } $v = str_replace(['€', ' '], '', $v); // Convert Italian format "1.234,56" to "1234.56". $v = str_replace('.', '', $v); $v = str_replace(',', '.', $v); if (! is_numeric($v)) { return null; } return round((float) $v, 2); } private function reloadDettaglio(string $codStabileLegacy, ?string $legacyYear): void { $this->detailMeta = []; $this->detailRows = []; if ($this->tab !== 'dettaglio' || ! $this->dett) { return; } $d = $this->dett; if (preg_match('/^(crediti|debiti)_(ordinaria|riscaldamento)$/', $d, $m)) { $cd = $m[1] === 'crediti' ? 'C' : 'D'; $bucket = $m[2]; $straFilter = $bucket === 'ordinaria' ? 'non_stra' : 'none'; $this->detailMeta = [ 'tipo' => 'cre_deb_preced', 'titolo' => strtoupper($m[1]) . ' · ' . strtoupper($bucket), ]; $this->detailRows = $this->loadCreDebPrecedRows($codStabileLegacy, $legacyYear, $cd, $straFilter); return; } if (preg_match('/^(crediti|debiti)_stra_(.+)$/', $d, $m)) { $cd = $m[1] === 'crediti' ? 'C' : 'D'; [$detailYear, $nStra] = $this->parseStraKey($m[2]); $effectiveYear = $detailYear ?? $legacyYear; $this->detailMeta = [ 'tipo' => 'cre_deb_preced', 'titolo' => strtoupper($m[1]) . ' · ' . $this->formatStraLabel($detailYear, (int) $nStra, ''), ]; $this->detailRows = $this->loadCreDebPrecedRows($codStabileLegacy, $effectiveYear, $cd, (int) $nStra); return; } if (preg_match('/^cong_(pos|neg)_(ordinaria|riscaldamento)$/', $d, $m)) { $sign = $m[1]; $bucket = $m[2]; $codTab = $bucket === 'ordinaria' ? 'CONG.O' : 'CONG.R'; $this->detailMeta = [ 'tipo' => 'dett_tab', 'titolo' => 'CONGUAGLI ' . ($sign === 'pos' ? '(+)' : '(-)') . ' · ' . strtoupper($bucket) . ' · ' . $codTab, ]; $this->detailRows = $this->loadDettTabRows($codStabileLegacy, $legacyYear, $codTab, 'non_stra', $sign); return; } if (preg_match('/^cong_(pos|neg)_stra_(.+)$/', $d, $m)) { $sign = $m[1]; [$detailYear, $nStra] = $this->parseStraKey($m[2]); $effectiveYear = $detailYear ?? $legacyYear; $this->detailMeta = [ 'tipo' => 'dett_tab', 'titolo' => 'CONGUAGLI ' . ($sign === 'pos' ? '(+)' : '(-)') . ' · ' . $this->formatStraLabel($detailYear, (int) $nStra, ''), ]; $this->detailRows = $this->loadDettTabRows($codStabileLegacy, $effectiveYear, null, (int) $nStra, $sign); return; } $this->detailMeta = [ 'tipo' => 'unknown', 'titolo' => 'Dettaglio non riconosciuto', ]; } private function loadCreDebPrecedRows(string $codStabileLegacy, ?string $legacyYear, string $cd, int|string $straFilter): array { if (! Schema::connection('gescon_import')->hasTable('cre_deb_preced')) { return []; } $q = DB::connection('gescon_import')->table('cre_deb_preced') ->whereRaw('UPPER(c_d) = ?', [strtoupper($cd)]); $hasForStabile = DB::connection('gescon_import')->table('cre_deb_preced') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } if (Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'incluso')) { $q->where('incluso', 1); } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } if ($straFilter === 'non_stra') { $q->where(function ($q) { $q->whereNull('n_stra')->orWhere('n_stra', '<=', 0); }); } elseif (is_int($straFilter) && $straFilter > 0) { $q->where('n_stra', $straFilter); } elseif ($straFilter === 'none') { // Nessun filtro: restituisce vuoto per bucket senza criterio. return []; } return $q ->orderBy('cod_voc') ->limit(5000) ->get(['c_d', 'cod_voc', 'des_voce', 'descrizione', 'importo_euro', 'n_stra']) ->map(fn ($r) => [ 'c_d' => (string) ($r->c_d ?? ''), 'cod_voc' => (string) ($r->cod_voc ?? ''), 'des_voce' => (string) ($r->des_voce ?? ''), 'descrizione' => (string) ($r->descrizione ?? ''), 'importo_euro' => (float) ($r->importo_euro ?? 0), 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, ]) ->all(); } private function loadDettTabRows(string $codStabileLegacy, ?string $legacyYear, ?string $codTab, int|string $straFilter, string $sign): array { if (! Schema::connection('gescon_import')->hasTable('dett_tab')) { return []; } $q = DB::connection('gescon_import')->table('dett_tab') ->where('cod_tab', 'like', 'CONG.%'); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $hasForStabile = DB::connection('gescon_import')->table('dett_tab') ->where('cod_stabile', $codStabileLegacy) ->limit(1) ->exists(); if ($hasForStabile) { $q->where('cod_stabile', $codStabileLegacy); } else { $q->where(function ($q) { $q->whereNull('cod_stabile')->orWhere('cod_stabile', ''); }); } } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } if ($codTab) { $q->where('cod_tab', $codTab); } if ($straFilter === 'non_stra') { $q->where(function ($q) { $q->whereNull('n_stra')->orWhere('n_stra', '<=', 0); }); } elseif (is_int($straFilter) && $straFilter > 0) { $q->where('n_stra', $straFilter); } if ($sign === 'pos') { $q->where('cons_euro', '>', 0); } else { $q->where('cons_euro', '<', 0); } return $q ->orderBy('cod_tab') ->orderBy('id_cond') ->limit(5000) ->get(['cod_tab', 'id_cond', 'cond_inquil', 'mm', 'cons_euro', 'n_stra', 'unico', 'proviene_ors', 'proviene_n_stra', 'proviene_eserc']) ->map(fn ($r) => [ 'cod_tab' => (string) ($r->cod_tab ?? ''), 'id_cond' => (string) ($r->id_cond ?? ''), 'cond_inquil' => (string) ($r->cond_inquil ?? ''), 'mm' => $r->mm !== null ? (float) $r->mm : null, 'cons_euro' => (float) ($r->cons_euro ?? 0), 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, 'unico' => (bool) ($r->unico ?? false), 'proviene_ors' => (string) ($r->proviene_ors ?? ''), 'proviene_n_stra' => $r->proviene_n_stra !== null ? (int) $r->proviene_n_stra : null, 'proviene_eserc' => (string) ($r->proviene_eserc ?? ''), ]) ->all(); } /** * @return array{0: string|null, 1: int} [legacy_year, n_stra] */ private function parseStraKey(string $raw): array { $raw = trim($raw); if ($raw === '') { return [null, 0]; } if (preg_match('/^(\d{4})\:(\d+)$/', $raw, $m)) { return [$m[1], (int) $m[2]]; } if (ctype_digit($raw)) { return [null, (int) $raw]; } return [null, 0]; } private function computeConguagliExcel(string $codStabileLegacy, ?string $legacyYear): array { if (! Schema::connection('gescon_import')->hasTable('dett_tab')) { return [[], []]; } $q = DB::connection('gescon_import')->table('dett_tab') ->where('cod_tab', 'like', 'CONG.%'); if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) { $q->where('cod_stabile', $codStabileLegacy); } if ($legacyYear && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { $q->where('legacy_year', $legacyYear); } $labels = $this->loadCondominiLabels($codStabileLegacy, $legacyYear); $rows = $q ->select([ 'id_cond', 'cod_tab', 'n_stra', DB::raw('SUM(cons_euro) as totale'), ]) ->groupBy('id_cond', 'cod_tab', 'n_stra') ->orderByRaw('CAST(id_cond AS UNSIGNED)') ->get() ->map(fn ($r) => [ 'id_cond' => (string) ($r->id_cond ?? ''), 'cond_label' => (string) ($labels[trim((string) ($r->id_cond ?? ''))] ?? ''), 'cod_tab' => (string) ($r->cod_tab ?? ''), 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, 'totale' => (float) ($r->totale ?? 0.0), ]) ->all(); // Merge manual conguagli if (DB::getSchemaBuilder()->hasTable('contabilita_situazione_iniziale_conguagli')) { $stabileId = $this->getActiveStabile()?->id; if ($stabileId) { $mq = DB::table('contabilita_situazione_iniziale_conguagli') ->where('stabile_id', (int) $stabileId) ->where('incluso', 1); if ($legacyYear) { $mq->where('legacy_year', $legacyYear); } $manual = $mq ->select([ 'id_cond', 'cod_tab', 'n_stra', DB::raw('SUM(cons_euro) as totale'), ]) ->groupBy('id_cond', 'cod_tab', 'n_stra') ->get() ->map(fn ($r) => [ 'id_cond' => (string) ($r->id_cond ?? ''), 'cod_tab' => (string) ($r->cod_tab ?? ''), 'n_stra' => $r->n_stra !== null ? (int) $r->n_stra : null, 'totale' => (float) ($r->totale ?? 0.0), ]) ->all(); // Sum into existing keys $acc = []; foreach ($rows as $r) { $k = $r['id_cond'] . '|' . $r['cod_tab'] . '|' . (string) ($r['n_stra'] ?? ''); $acc[$k] = $r; } foreach ($manual as $r) { $k = $r['id_cond'] . '|' . $r['cod_tab'] . '|' . (string) ($r['n_stra'] ?? ''); if (isset($acc[$k])) { $acc[$k]['totale'] = (float) ($acc[$k]['totale'] ?? 0.0) + (float) ($r['totale'] ?? 0.0); } else { $acc[$k] = $r; } } $rows = array_values($acc); } } $cols = []; foreach ($rows as $r) { $key = $r['cod_tab'] . '|' . (string) ($r['n_stra'] ?? ''); $cols[$key] = [ 'key' => $key, 'label' => $r['cod_tab'] . (($r['n_stra'] ?? null) ? (' #' . $r['n_stra']) : ''), 'cod_tab' => $r['cod_tab'], 'n_stra' => $r['n_stra'], ]; } $cols = array_values($cols); usort($cols, fn ($a, $b) => strcmp((string) $a['label'], (string) $b['label'])); $matrix = []; foreach ($rows as $r) { $idCond = $r['id_cond'] ?: '—'; if (! isset($matrix[$idCond])) { $matrix[$idCond] = ['id_cond' => $idCond, 'cells' => []]; } $key = $r['cod_tab'] . '|' . (string) ($r['n_stra'] ?? ''); $matrix[$idCond]['cells'][$key] = (float) ($r['totale'] ?? 0.0); } $out = array_values($matrix); usort($out, fn ($a, $b) => (int) ($a['id_cond'] ?? 0) <=> (int) ($b['id_cond'] ?? 0)); return [$cols, $out]; } public function addVoceManuale(): void { $stabileId = $this->getActiveStabile()?->id; if (! $stabileId) { return; } if (! DB::getSchemaBuilder()->hasTable('contabilita_situazione_iniziale_voci')) { return; } $gestione = is_string($this->formVoce['gestione'] ?? null) ? trim((string) $this->formVoce['gestione']) : ''; $categoria = is_string($this->formVoce['categoria'] ?? null) ? trim((string) $this->formVoce['categoria']) : ''; $gestione = in_array($gestione, ['ordinaria', 'riscaldamento', 'straordinaria'], true) ? $gestione : 'ordinaria'; $categoria = in_array($categoria, ['crediti', 'debiti'], true) ? $categoria : 'crediti'; $nStra = $gestione === 'straordinaria' && is_numeric($this->formVoce['n_stra'] ?? null) ? (int) $this->formVoce['n_stra'] : null; if ($gestione === 'straordinaria' && (! $nStra || $nStra <= 0)) { return; } $importo = $this->parseMoney(is_string($this->formVoce['importo_euro'] ?? null) ? (string) $this->formVoce['importo_euro'] : null); if ($importo === null) { return; } DB::table('contabilita_situazione_iniziale_voci')->insert([ 'stabile_id' => (int) $stabileId, 'legacy_year' => $this->legacyYear, 'gestione' => $gestione, 'n_stra' => $gestione === 'straordinaria' ? $nStra : null, 'categoria' => $categoria, 'codice' => is_string($this->formVoce['codice'] ?? null) ? trim((string) $this->formVoce['codice']) : null, 'descrizione' => is_string($this->formVoce['descrizione'] ?? null) ? trim((string) $this->formVoce['descrizione']) : null, 'importo_euro' => $importo, 'incluso' => 1, 'origine' => 'manuale', 'created_at' => now(), 'updated_at' => now(), ]); $this->formVoce['codice'] = null; $this->formVoce['descrizione'] = null; $this->formVoce['importo_euro'] = null; $this->reloadData(); } public function addConguaglioManuale(): void { $stabileId = $this->getActiveStabile()?->id; if (! $stabileId) { return; } if (! DB::getSchemaBuilder()->hasTable('contabilita_situazione_iniziale_conguagli')) { return; } $codTab = is_string($this->formConguaglio['cod_tab'] ?? null) ? trim((string) $this->formConguaglio['cod_tab']) : ''; if ($codTab === '') { return; } $idCond = is_string($this->formConguaglio['id_cond'] ?? null) ? trim((string) $this->formConguaglio['id_cond']) : ''; if ($idCond === '') { return; } $nStra = is_numeric($this->formConguaglio['n_stra'] ?? null) ? (int) $this->formConguaglio['n_stra'] : null; $cons = $this->parseMoney(is_string($this->formConguaglio['cons_euro'] ?? null) ? (string) $this->formConguaglio['cons_euro'] : null); if ($cons === null) { return; } DB::table('contabilita_situazione_iniziale_conguagli')->insert([ 'stabile_id' => (int) $stabileId, 'legacy_year' => $this->legacyYear, 'cod_tab' => $codTab, 'n_stra' => $nStra && $nStra > 0 ? $nStra : null, 'id_cond' => $idCond, 'cons_euro' => $cons, 'incluso' => 1, 'origine' => 'manuale', 'created_at' => now(), 'updated_at' => now(), ]); $this->formConguaglio['id_cond'] = null; $this->formConguaglio['cons_euro'] = null; $this->reloadData(); } }