793 lines
31 KiB
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();
|
|
}
|
|
}
|