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

793 lines
31 KiB
PHP

<?php
namespace App\Filament\Pages\Contabilita;
use App\Filament\Pages\Contabilita\CasseBancheMovimenti;
use App\Models\Incasso;
use App\Models\RataEmessaNg;
use App\Models\Soggetto;
use App\Models\UnitaImmobiliare;
use App\Models\User;
use App\Support\AnnoGestioneContext;
use App\Support\StabileContext;
use BackedEnum;
use Carbon\Carbon;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema as DbSchema;
use UnitEnum;
class EstrattoContoSoggetto extends Page
{
protected static ?string $title = 'Estratto conto soggetto';
protected static ?string $slug = 'contabilita/estratto-conto/{record}';
protected static bool $shouldRegisterNavigation = false;
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-document-text';
protected static UnitEnum|string|null $navigationGroup = 'Stabile';
protected string $view = 'filament.pages.contabilita.estratto-conto-soggetto';
public ?Soggetto $soggetto = null;
/** Vista: 'unita' (singola unità) oppure 'soggetto' (tutte le unità del soggetto) */
public string $vista = 'unita';
/** Ricerca unità (codice/int.) */
public string $qUnita = '';
/** Unità selezionata (per vista=unita) */
public ?int $unitaId = null;
public ?int $prevUnitaId = null;
public ?int $nextUnitaId = null;
/** Link rapidi: estratto conto condomino/inquilino per la stessa unità (se presente) */
public ?int $recordCondominoId = null;
public ?int $recordInquilinoId = null;
/** Mostra colonna scadenza */
public bool $showScadenza = false;
/** @var array<int, array{id:int,label:string,short:string,totale:float,residuo:float}> */
public array $unitaList = [];
/** @var array<int, array<string, mixed>> */
public array $rows = [];
/** @var array<int, array{key:string,label:string,rows:array<int, array<string,mixed>>,totali:array{dovuto:float,pagato:float,residuo:float}}> */
public array $groups = [];
/** @var array<int, array<string, mixed>> */
public array $incassi = [];
/** @var array{addebitato:float,pagato:float,residuo:float,rate:int} */
public array $totali = [
'addebitato' => 0.0,
'pagato' => 0.0,
'residuo' => 0.0,
'rate' => 0,
];
/** @var array{addebitato:float,pagato:float,residuo:float,rate:int} */
public array $totaliUnita = [
'addebitato' => 0.0,
'pagato' => 0.0,
'residuo' => 0.0,
'rate' => 0,
];
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
}
public function mount(int|string $record): void
{
$user = Auth::user();
if (! $user instanceof User) {
abort(403);
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()
->title('Stabile non selezionato')
->body('Seleziona uno stabile per consultare l\'estratto conto.')
->warning()
->send();
return;
}
$req = request();
$vista = trim((string) $req->query('vista', 'unita'));
$this->vista = in_array($vista, ['unita', 'soggetto'], true) ? $vista : 'unita';
$this->qUnita = trim((string) $req->query('q', ''));
$this->showScadenza = $req->boolean('scadenza');
$unitaIdRaw = $req->query('unita_id');
$this->unitaId = is_numeric($unitaIdRaw) ? (int) $unitaIdRaw : null;
if ($this->unitaId === null) {
try {
$ref = (string) ($req->headers->get('referer') ?? '');
if ($ref !== '') {
$q = parse_url($ref, PHP_URL_QUERY);
if (is_string($q) && $q !== '') {
parse_str($q, $params);
$refUnita = $params['unita_id'] ?? null;
if (is_numeric($refUnita)) {
$this->unitaId = (int) $refUnita;
}
}
}
} catch (\Throwable $e) {
// ignore
}
}
$this->soggetto = Soggetto::query()->findOrFail((int) $record);
if (! DbSchema::hasTable('rate_emesse')) {
return;
}
$query = RataEmessaNg::query()
->where('soggetto_responsabile_id', (int) $this->soggetto->id)
->whereHas('unitaImmobiliare', function ($q) use ($stabileId): void {
$q->where('stabile_id', (int) $stabileId);
})
->with([
'unitaImmobiliare:id,stabile_id,codice_unita,denominazione,scala,piano,interno',
'pianoRateizzazione:id,stabile_id,descrizione',
])
->orderBy('data_emissione')
->orderBy('id')
->limit(5000);
$annoGestione = AnnoGestioneContext::resolveActiveAnno($user);
$codStabile = $user instanceof User ? (StabileContext::getActiveStabile($user)?->codice_stabile ?? '') : '';
if ($annoGestione && $codStabile && DbSchema::connection('gescon_import')->hasTable('rate_emissioni')) {
$legacyAnni = DB::connection('gescon_import')->table('rate_emissioni')
->where('cod_stabile', (string) $codStabile)
->whereNotNull('anno_emissione')
->whereYear('data_emissione', (int) $annoGestione)
->pluck('anno_emissione')
->map(fn($v) => trim((string) $v))
->filter(fn($v) => $v !== '')
->unique()
->values()
->all();
if (!empty($legacyAnni)) {
$query->where(function ($q) use ($legacyAnni): void {
foreach ($legacyAnni as $legacyAnno) {
$q->orWhereRaw(
"SUBSTRING_INDEX(SUBSTRING_INDEX(note, 'anno_emissione=', -1), '|', 1) = ?",
[$legacyAnno]
);
}
});
}
}
$items = $query->get();
// Totali soggetto (tutte le unità)
$this->totali['rate'] = (int) $items->count();
$this->totali['addebitato'] = (float) $items->sum(fn (RataEmessaNg $r) => (float) $r->importo_addebitato_soggetto);
$this->totali['pagato'] = (float) $items->sum(fn (RataEmessaNg $r) => (float) $r->importo_pagato);
$this->totali['residuo'] = (float) ($this->totali['addebitato'] - $this->totali['pagato']);
// Unità disponibili per questo soggetto
$unitaMap = [];
foreach ($items as $r) {
$u = $r->unitaImmobiliare;
if (! ($u instanceof UnitaImmobiliare)) {
continue;
}
$unitaMap[$u->id] = $u;
}
$unitaAll = array_values($unitaMap);
usort($unitaAll, function (UnitaImmobiliare $a, UnitaImmobiliare $b): int {
$ak = (string) ($a->codice_unita ?? '');
$bk = (string) ($b->codice_unita ?? '');
if ($ak !== '' && $bk !== '') {
return strcmp($ak, $bk);
}
return $a->id <=> $b->id;
});
$unitaFiltered = $unitaAll;
if ($this->qUnita !== '') {
$needle = mb_strtolower($this->qUnita);
$unitaFiltered = array_values(array_filter($unitaFiltered, function (UnitaImmobiliare $u) use ($needle): bool {
$hay = mb_strtolower(
trim((string) ($u->codice_unita ?? '') . ' ' . (string) ($u->interno ?? '') . ' ' . (string) ($u->denominazione ?? ''))
);
return $hay !== '' && str_contains($hay, $needle);
}));
}
if ($this->unitaId === null || ! array_key_exists($this->unitaId, $unitaMap)) {
$this->unitaId = ! empty($unitaFiltered) ? (int) $unitaFiltered[0]->id : (! empty($unitaAll) ? (int) $unitaAll[0]->id : null);
}
$this->unitaList = [];
foreach ($unitaFiltered as $u) {
$short = $this->formatUnitaShort($u);
$tot = (float) $items->where('unita_immobiliare_id', $u->id)->sum(fn (RataEmessaNg $r) => (float) $r->importo_addebitato_soggetto);
$pag = (float) $items->where('unita_immobiliare_id', $u->id)->sum(fn (RataEmessaNg $r) => (float) $r->importo_pagato);
$this->unitaList[] = [
'id' => (int) $u->id,
'label' => $this->formatUnitaLabel($u),
'short' => $short,
'totale' => $tot,
'residuo' => $tot - $pag,
];
}
$this->prevUnitaId = null;
$this->nextUnitaId = null;
if ($this->unitaId !== null && ! empty($this->unitaList)) {
$idx = null;
foreach ($this->unitaList as $i => $u) {
if ((int) $u['id'] === (int) $this->unitaId) {
$idx = $i;
break;
}
}
if ($idx !== null) {
$this->prevUnitaId = $idx > 0 ? (int) $this->unitaList[$idx - 1]['id'] : null;
$this->nextUnitaId = $idx < (count($this->unitaList) - 1) ? (int) $this->unitaList[$idx + 1]['id'] : null;
}
}
// Determina se esistono quote inquilino/condomino per la stessa unità (per link rapido)
if ($this->unitaId !== null) {
$baseOther = RataEmessaNg::query()
->where('unita_immobiliare_id', (int) $this->unitaId)
->whereHas('unitaImmobiliare', function ($q) use ($stabileId): void {
$q->where('stabile_id', (int) $stabileId);
})
->whereNotNull('note');
$condIds = (clone $baseOther)
->where('note', 'like', '%tipo_soggetto=C%')
->distinct()
->pluck('soggetto_responsabile_id')
->map(fn ($v) => (int) $v)
->values();
$inqIds = (clone $baseOther)
->where('note', 'like', '%tipo_soggetto=I%')
->distinct()
->pluck('soggetto_responsabile_id')
->map(fn ($v) => (int) $v)
->values();
$this->recordCondominoId = $condIds->count() === 1 ? (int) $condIds[0] : null;
$this->recordInquilinoId = $inqIds->count() === 1 ? (int) $inqIds[0] : null;
}
$aggregateItems = function ($collection) {
return $collection
->groupBy(function (RataEmessaNg $r): string {
return (int) ($r->unita_immobiliare_id ?? 0)
. '|' . (int) ($r->piano_rateizzazione_id ?? 0)
. '|' . (int) ($r->numero_rata_progressivo ?? 0);
})
->map(function ($group) {
/** @var RataEmessaNg $first */
$first = $group->first();
if ($group->count() > 1) {
$first->importo_addebitato_soggetto = $group->sum('importo_addebitato_soggetto');
$first->importo_pagato = $group->sum('importo_pagato');
}
return $first;
})
->values();
};
$itemsAggregati = $aggregateItems($items);
// Filtra righe in base alla vista scelta
$displayItems = $itemsAggregati;
if ($this->vista === 'unita' && $this->unitaId !== null) {
$displayItems = $itemsAggregati->where('unita_immobiliare_id', (int) $this->unitaId)->values();
}
// Pre-carica saldi iniziali (esercizi precedenti) da dett_tab importato (CONG.*)
$saldoRowsAll = $this->loadSaldiInizialiRows((int) $stabileId, $items, $unitaAll);
$saldoRowsDisplay = $saldoRowsAll;
if ($this->vista === 'unita' && $this->unitaId !== null) {
$saldoRowsDisplay = array_values(array_filter($saldoRowsAll, fn ($r) => (int) ($r['unita_id'] ?? 0) === (int) $this->unitaId));
}
$this->totaliUnita['rate'] = $this->unitaId !== null ? (int) $displayItems->count() : 0;
$this->totaliUnita['addebitato'] = $this->unitaId !== null ? (float) $displayItems->sum(fn (RataEmessaNg $r) => (float) $r->importo_addebitato_soggetto) : 0.0;
$this->totaliUnita['pagato'] = $this->unitaId !== null ? (float) $displayItems->sum(fn (RataEmessaNg $r) => (float) $r->importo_pagato) : 0.0;
$this->totaliUnita['residuo'] = (float) ($this->totaliUnita['addebitato'] - $this->totaliUnita['pagato']);
$rateRows = $displayItems
->map(function (RataEmessaNg $r): array {
$u = $r->unitaImmobiliare;
$pianoId = is_numeric($r->piano_rateizzazione_id ?? null) ? (int) $r->piano_rateizzazione_id : 0;
$pianoLabel = (string) ($r->pianoRateizzazione?->descrizione ?? '');
$unitaShort = '';
if ($u instanceof UnitaImmobiliare) {
$unitaShort = $this->formatUnitaShort($u);
}
$note = (string) ($r->note ?? '');
$nEmissione = null;
if ($note !== '' && preg_match('/\bn_emissione=(\d+)\b/', $note, $m)) {
$nEmissione = (int) ($m[1] ?? 0);
}
$dovuto = (float) ($r->importo_addebitato_soggetto ?? 0);
$pagato = (float) ($r->importo_pagato ?? 0);
$residuo = $dovuto - $pagato;
return [
'id' => (int) $r->id,
'piano_id' => $pianoId,
'piano' => $pianoLabel,
'data_emissione' => $r->data_emissione?->format('d/m/Y'),
'data_emissione_sort' => $r->data_emissione?->format('Y-m-d') ?? '9999-12-31',
'data_scadenza' => $r->data_scadenza?->format('d/m/Y'),
'avviso' => (int) ($r->numero_rata_progressivo ?? 0),
'n_emissione' => $nEmissione,
'descrizione' => (string) ($r->descrizione ?? ''),
'unita_short' => $unitaShort,
'dovuto' => $dovuto,
'data_pagamento' => $r->data_ultimo_pagamento?->format('d/m/Y'),
'pagato' => $pagato,
'residuo' => $residuo,
];
})
->all();
// Ordina: saldi iniziali prima delle rate
$this->rows = array_merge($saldoRowsDisplay, $rateRows);
// Aggiorna totali includendo i saldi iniziali (solo dovuto/residuo)
$saldoAll = array_sum(array_map(fn ($r) => (float) ($r['dovuto'] ?? 0), $saldoRowsAll));
$saldoDisplay = array_sum(array_map(fn ($r) => (float) ($r['dovuto'] ?? 0), $saldoRowsDisplay));
$this->totali['addebitato'] = (float) ($this->totali['addebitato'] + $saldoAll);
$this->totali['residuo'] = (float) ($this->totali['addebitato'] - $this->totali['pagato']);
$this->totaliUnita['addebitato'] = (float) ($this->totaliUnita['addebitato'] + $saldoDisplay);
$this->totaliUnita['residuo'] = (float) ($this->totaliUnita['addebitato'] - $this->totaliUnita['pagato']);
$this->groups = $this->buildGroups($this->rows);
$this->hydrateIncassiForSoggetto((int) $stabileId, $items, $annoGestione ? (int) $annoGestione : null);
}
private function formatUnitaLabel(UnitaImmobiliare $u): string
{
$cod = (string) ($u->codice_unita ?? ('ID ' . $u->id));
$nome = trim((string) ($u->denominazione ?? ''));
if ($nome === '' || preg_match('/^\s*area\s+condominiale\s*$/i', $nome)) {
$nome = 'Unità';
}
$pos = trim('Scala ' . ($u->scala ?? '-') . ' · Piano ' . ($u->piano ?? '-') . ' · Int. ' . ($u->interno ?? '-'));
return trim($cod . ' — ' . $nome . ' (' . $pos . ')');
}
private function formatUnitaShort(UnitaImmobiliare $u): string
{
$scala = trim((string) ($u->scala ?? ''));
$piano = trim((string) ($u->piano ?? ''));
$interno = trim((string) ($u->interno ?? ''));
$parts = [];
if ($scala !== '') {
$parts[] = $scala;
}
if ($piano !== '') {
$parts[] = 'P' . $piano;
}
if ($interno !== '') {
$parts[] = 'Int.' . $interno;
}
return implode('-', $parts);
}
/** @param array<int, array<string,mixed>> $rows */
private function buildGroups(array $rows): array
{
$rank = function (string $label): int {
$l = mb_strtolower($label);
if (str_contains($l, 'bilancio iniziale') || str_contains($l, 'situazione iniziale') || str_contains($l, 'esercizi precedenti')) {
return -1;
}
if (str_contains($l, 'ordinaria')) {
return 0;
}
if (str_contains($l, 'riscaldamento')) {
return 1;
}
if (str_contains($l, 'straordinaria')) {
return 2;
}
return 9;
};
$by = [];
foreach ($rows as $r) {
$key = (string) (($r['piano_id'] ?? 0) ?: ($r['piano'] ?? ''));
if (! array_key_exists($key, $by)) {
$by[$key] = [
'key' => $key,
'label' => (string) ($r['piano'] ?? 'Gestione'),
'rows' => [],
'min_emissione' => (string) ($r['data_emissione_sort'] ?? '9999-12-31'),
'rank' => $rank((string) ($r['piano'] ?? '')),
'totali' => ['dovuto' => 0.0, 'pagato' => 0.0, 'residuo' => 0.0],
];
}
$by[$key]['rows'][] = $r;
$by[$key]['totali']['dovuto'] += (float) ($r['dovuto'] ?? 0);
$by[$key]['totali']['pagato'] += (float) ($r['pagato'] ?? 0);
$by[$key]['totali']['residuo'] += (float) ($r['residuo'] ?? 0);
$d = (string) ($r['data_emissione_sort'] ?? '9999-12-31');
if ($d < $by[$key]['min_emissione']) {
$by[$key]['min_emissione'] = $d;
}
}
$groups = array_values($by);
usort($groups, function ($a, $b): int {
$ra = (int) ($a['rank'] ?? 9);
$rb = (int) ($b['rank'] ?? 9);
if ($ra !== $rb) {
return $ra <=> $rb;
}
return strcmp((string) $a['min_emissione'], (string) $b['min_emissione']);
});
foreach ($groups as &$g) {
usort($g['rows'], function ($a, $b): int {
$da = (string) ($a['data_emissione_sort'] ?? '9999-12-31');
$db = (string) ($b['data_emissione_sort'] ?? '9999-12-31');
if ($da === $db) {
return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
}
return strcmp($da, $db);
});
unset($g['min_emissione']);
unset($g['rank']);
}
return $groups;
}
/**
* Costruisce righe "Bilancio iniziale / esercizi precedenti" per unità e ruolo (C/I) dal dominio.
* Per evitare collisioni tra import multipli, usa la prima (min) legacy_year disponibile per ogni unità/ruolo.
*
* @param \Illuminate\Support\Collection<int, RataEmessaNg> $rateItems
* @param array<int, UnitaImmobiliare> $unitaAll
* @return array<int, array<string, mixed>>
*/
private function loadSaldiInizialiRows(int $stabileId, $rateItems, array $unitaAll): array
{
if (! DbSchema::hasTable('dettaglio_importi_tabella') || ! DbSchema::hasTable('tabelle_millesimali')) {
return [];
}
if (! DbSchema::hasColumn('tabelle_millesimali', 'stabile_id') || ! DbSchema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
return [];
}
$unitIds = array_values(array_unique(array_map(fn (UnitaImmobiliare $u) => (int) $u->id, $unitaAll)));
if (empty($unitIds)) {
return [];
}
// Inferisci tipo soggetto (C/I) per unità dalle note delle rate
$tipoPerUnita = [];
foreach ($rateItems as $r) {
try {
$uid = is_numeric($r->unita_immobiliare_id ?? null) ? (int) $r->unita_immobiliare_id : null;
if (! $uid) {
continue;
}
if (array_key_exists($uid, $tipoPerUnita)) {
continue;
}
$note = (string) ($r->note ?? '');
if ($note !== '' && preg_match('/\btipo_soggetto=([CI])\b/', $note, $m)) {
$tipoPerUnita[$uid] = (string) ($m[1] ?? '');
}
} catch (\Throwable $e) {
continue;
}
}
$domainHasLegacyYear = DbSchema::hasColumn('dettaglio_importi_tabella', 'legacy_year');
$base = DB::table('dettaglio_importi_tabella as dit')
->join('tabelle_millesimali as tm', 'tm.id', '=', 'dit.tabella_millesimale_id')
->where('tm.stabile_id', $stabileId)
->whereIn('dit.unita_immobiliare_id', $unitIds)
->where('tm.codice_tabella', 'like', 'CONG.%')
->whereNotNull('dit.cons_euro');
// Se c'è legacy_year, seleziona solo la prima (min) per unità+ruolo per ottenere la "situazione iniziale"
if ($domainHasLegacyYear) {
$minYears = (clone $base)
->selectRaw('dit.unita_immobiliare_id, dit.ruolo_legacy, MIN(dit.legacy_year) as legacy_year')
->groupBy('dit.unita_immobiliare_id', 'dit.ruolo_legacy');
$base->joinSub($minYears, 'mn', function ($join) {
$join->on('mn.unita_immobiliare_id', '=', 'dit.unita_immobiliare_id')
->on('mn.ruolo_legacy', '=', 'dit.ruolo_legacy')
->on('mn.legacy_year', '=', 'dit.legacy_year');
});
}
$rows = $base
->selectRaw('dit.unita_immobiliare_id as unita_id, dit.ruolo_legacy as ruolo, tm.codice_tabella as codice_tabella, SUM(dit.cons_euro) as importo')
->groupBy('dit.unita_immobiliare_id', 'dit.ruolo_legacy', 'tm.codice_tabella')
->get();
if ($rows->isEmpty()) {
return [];
}
$unitaById = [];
foreach ($unitaAll as $u) {
$unitaById[(int) $u->id] = $u;
}
$out = [];
$seq = 0;
foreach ($rows as $r) {
$unitaId = is_numeric($r->unita_id ?? null) ? (int) $r->unita_id : 0;
if (! $unitaId || ! array_key_exists($unitaId, $unitaById)) {
continue;
}
$ruolo = strtoupper(trim((string) ($r->ruolo ?? '')));
$tipoAtteso = $tipoPerUnita[$unitaId] ?? null;
if ($tipoAtteso && $ruolo !== $tipoAtteso) {
continue;
}
$importo = is_numeric($r->importo ?? null) ? (float) $r->importo : 0.0;
if (abs($importo) < 0.00001) {
continue;
}
$u = $unitaById[$unitaId];
$unitaShort = $this->formatUnitaShort($u);
$codTab = trim((string) ($r->codice_tabella ?? ''));
$seq++;
$out[] = [
'id' => -1000000 - $seq,
'piano_id' => 0,
'piano' => 'Bilancio iniziale (esercizi precedenti)',
'data_emissione' => '',
'data_emissione_sort' => '1900-01-01',
'data_scadenza' => null,
'avviso' => 0,
'n_emissione' => null,
'descrizione' => 'Conguagli es. precedenti (' . $codTab . ')',
'unita_short' => $unitaShort,
'dovuto' => $importo,
'data_pagamento' => null,
'pagato' => 0.0,
'residuo' => $importo,
'unita_id' => $unitaId,
'ruolo' => $ruolo,
];
}
return $out;
}
/**
* Collega incassi al soggetto usando la stessa codifica d'unione delle rate:
* - tipo_soggetto=([CI])
* - cod_cond_dest=...
*
* In incassi: cond_inquil + cod_cond_gescon (o cod_cond)
*/
private function hydrateIncassiForSoggetto(int $stabileId, $rateItems, ?int $annoGestione = null): void
{
$this->incassi = [];
if (! DbSchema::hasTable('incassi')) {
return;
}
$codCol = DbSchema::hasColumn('incassi', 'cod_cond_gescon')
? 'cod_cond_gescon'
: (DbSchema::hasColumn('incassi', 'cod_cond') ? 'cod_cond' : null);
$tipoCol = DbSchema::hasColumn('incassi', 'cond_inquil')
? 'cond_inquil'
: (DbSchema::hasColumn('incassi', 'cond_inq') ? 'cond_inq' : null);
if (! $codCol || ! $tipoCol) {
return;
}
$pairs = [];
foreach ($rateItems as $r) {
try {
$note = (string) ($r->note ?? '');
if ($note === '') {
continue;
}
$tipo = '';
$cod = '';
if (preg_match('/\btipo_soggetto=([CI])\b/', $note, $mTipo)) {
$tipo = (string) ($mTipo[1] ?? '');
} elseif (preg_match('/\bcond_inq=([CI])\b/', $note, $mTipoAlt)) {
$tipo = (string) ($mTipoAlt[1] ?? '');
}
if (preg_match('/\bcod_cond_dest=([^\s|]+)/', $note, $mCod)) {
$cod = trim((string) ($mCod[1] ?? ''));
} elseif (preg_match('/\bcod_cond_src=([^\s|]+)/', $note, $mCodAlt)) {
$cod = trim((string) ($mCodAlt[1] ?? ''));
}
if ($tipo === '' || $cod === '') {
continue;
}
$pairs[$tipo . '|' . $cod] = ['tipo' => $tipo, 'cod' => $cod];
} catch (\Throwable $e) {
continue;
}
}
if (empty($pairs)) {
return;
}
$incassiQuery = Incasso::query();
// Per NetGescon NG, l'import incassi salva condominio_id = stabileId.
if (DbSchema::hasColumn('incassi', 'condominio_id')) {
$incassiQuery->where('condominio_id', $stabileId);
}
$incassiQuery->where(function ($q) use ($pairs, $codCol, $tipoCol): void {
foreach ($pairs as $p) {
$q->orWhere(function ($qq) use ($p, $codCol, $tipoCol): void {
$qq->where($codCol, (string) $p['cod'])
->where($tipoCol, (string) $p['tipo']);
});
}
});
if ($annoGestione) {
$annoCol = DbSchema::hasColumn('incassi', 'anno_rif')
? 'anno_rif'
: (DbSchema::hasColumn('incassi', 'anno') ? 'anno' : null);
$annoShort = (int) substr((string) $annoGestione, -2);
$incassiQuery->where(function ($q) use ($annoCol, $annoGestione, $annoShort): void {
if ($annoCol) {
$q->where($annoCol, (string) $annoGestione)
->orWhere($annoCol, (string) $annoShort)
->orWhere($annoCol, str_pad((string) $annoShort, 2, '0', STR_PAD_LEFT));
}
if (DbSchema::hasColumn('incassi', 'dt_empag')) {
$q->orWhereYear('dt_empag', (int) $annoGestione);
}
if (DbSchema::hasColumn('incassi', 'data_pagamento')) {
$q->orWhereYear('data_pagamento', (int) $annoGestione);
}
});
}
$orderCol = DbSchema::hasColumn('incassi', 'dt_empag')
? 'dt_empag'
: (DbSchema::hasColumn('incassi', 'data_pagamento') ? 'data_pagamento' : 'id');
$incassi = $incassiQuery
->orderByDesc($orderCol)
->orderByDesc('id')
->limit(2000)
->get();
$contoMovimentiBase = CasseBancheMovimenti::getUrl(panel: 'admin-filament');
$this->incassi = $incassi
->map(function (Incasso $i) use ($codCol, $tipoCol, $contoMovimentiBase): array {
$codCond = (string) (data_get($i, $codCol) ?? '');
$tipo = (string) (data_get($i, $tipoCol) ?? '');
$importo = (float) ($i->importo_pagato_euro ?? $i->importo_pagato ?? 0);
$dt = null;
if (! empty($i->dt_empag)) {
try {
$dt = Carbon::parse($i->dt_empag);
} catch (\Throwable $e) {
$dt = null;
}
}
$dp = null;
if (! empty($i->data_pagamento)) {
try {
$dp = Carbon::parse($i->data_pagamento);
} catch (\Throwable $e) {
$dp = null;
}
}
$annoRaw = $i->anno_rif;
$annoRawStr = is_null($annoRaw) ? '' : (string) $annoRaw;
$annoNum = is_numeric($annoRawStr) ? (int) $annoRawStr : null;
$annoUmano = null;
if ($annoNum !== null && $annoNum >= 1900) {
$annoUmano = $annoNum;
} elseif ($dt) {
$annoUmano = (int) $dt->format('Y');
} elseif ($dp) {
$annoUmano = (int) $dp->format('Y');
}
$annoLabel = $annoUmano ? (string) $annoUmano : ($annoRawStr !== '' ? ltrim($annoRawStr, '0') : '—');
if ($annoUmano && $annoNum !== null && $annoNum > 0 && $annoNum < 1900) {
$annoLabel .= ' (' . str_pad((string) $annoNum, 4, '0', STR_PAD_LEFT) . ')';
}
$contoId = is_numeric($i->conto_bancario_id ?? null) ? (int) $i->conto_bancario_id : null;
$contoUrl = $contoId ? ($contoMovimentiBase . '?conto_id=' . $contoId) : null;
$stato = trim((string) ($i->stato_riconciliazione ?? ''));
$conf = is_numeric($i->confidenza_match ?? null) ? (float) $i->confidenza_match : null;
$hasMov = ! empty($i->movimento_bancario_id);
$semaforo = 'DA RICONCILIARE';
if ($hasMov || strtolower($stato) === 'riconciliato') {
$semaforo = 'OK';
} elseif ($conf !== null && $conf >= 0.7) {
$semaforo = 'MATCH';
}
$codCassa = (string) ($i->cod_cassa_gescon ?? $i->cod_cassa ?? $i->cod_cassa_legacy ?? $i->cod_cassa_gescon ?? '');
return [
'id' => (int) $i->id,
'tipo' => $tipo,
'cod_cond' => $codCond,
'anno' => $annoLabel,
'n_ricevuta' => $i->n_ricevuta ?? null,
'dt_empag' => $dt ? $dt->format('d/m/Y') : null,
'data_pagamento' => $dp ? $dp->format('d/m/Y') : null,
'importo' => $importo,
'descrizione' => (string) ($i->descrizione ?? ''),
'cod_cassa' => $codCassa,
'conto_id' => $contoId,
'conto_url' => $contoUrl,
'stato_riconciliazione' => $stato,
'confidenza_match' => $conf,
'semaforo' => $semaforo,
];
})
->all();
}
}