netgescon-day0/app/Filament/Pages/Contabilita/SituazioneIniziale.php

2364 lines
89 KiB
PHP

<?php
namespace App\Filament\Pages\Contabilita;
use App\Models\DatiBancari;
use App\Models\FatturaElettronica;
use App\Models\GestioneContabile;
use App\Models\RegistroRitenuteAcconto;
use App\Models\Stabile;
use App\Models\User;
use App\Modules\Contabilita\Models\MovimentoBanca;
use App\Modules\Contabilita\Models\SaldoConto;
use App\Support\StabileContext;
use Filament\Pages\Page;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Livewire\Attributes\Url;
use UnitEnum;
use BackedEnum;
class SituazioneIniziale extends Page
{
protected static ?string $navigationLabel = 'Situazione iniziale';
protected static ?string $title = 'Situazione iniziale';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-scale';
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
protected static ?int $navigationSort = 12;
protected static ?string $slug = 'contabilita/situazione-iniziale';
protected string $view = 'filament.pages.contabilita.situazione-iniziale';
#[Url(as: 'legacy_year')]
public ?string $legacyYear = null;
#[Url(as: 'data')]
public ?string $dataBilancio = null;
#[Url(as: 'saldo_manuale')]
public ?string $saldoBancaManuale = null;
#[Url(as: 'tab')]
public string $tab = 'bilancio';
#[Url(as: 'dett')]
public ?string $dett = null;
public array $legacyYears = [];
/** @var array<string, string> 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<int, array<string, mixed>> */
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<int, array<string, mixed>> */
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<string, string> */
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<int, array<string, mixed>> */
public array $feRows = [];
/** @var array<int, int> */
public array $linkedFeIds = [];
/** @var array<int, array<string, mixed>> */
public array $raRows = [];
/** @var array<int, int> */
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<int, array<string, mixed>>
*/
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<int, array<string, mixed>>
*/
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<int, int>
*/
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<int, int>
*/
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<string, string>
*/
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<int, array{key: string, legacy_year: string|null, n_stra: int}>
*/
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();
}
}