netgescon-day0/app/Filament/Pages/Gescon/Ordinarie.php

4060 lines
163 KiB
PHP
Executable File

<?php
namespace App\Filament\Pages\Gescon;
use App\Models\User;
use App\Models\UserSetting;
use App\Services\Contabilita\PreventivoOrdinarioPreviewService;
use App\Support\AnnoGestioneContext;
use App\Support\ModuleVisibility;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Livewire\WithPagination;
use UnitEnum;
class Ordinarie extends Page
{
use WithPagination;
protected static ?string $navigationLabel = 'Gest. ordinaria';
protected static ?string $title = 'Gestione ordinaria';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-calculator';
protected static UnitEnum|string|null $navigationGroup = 'GESCON';
protected static ?int $navigationSort = 20;
protected static ?string $slug = 'gescon/ordinarie';
protected string $view = 'filament.pages.gescon.section';
public string $sectionKey = 'ordinarie';
public int $perPage = 50;
public ?string $search = null;
public ?string $filterCodForn = null;
public ?string $filterCodSpe = null;
public ?string $filterTabella = null;
public ?string $filterBenef = null;
public bool $filterRdaOnly = false;
public ?int $filterAnno = null;
public string $operazioniGestioneFilter = 'all';
public string $viewTab = 'operazioni';
public string $sortField = 'id_operaz';
public string $sortDirection = 'desc';
public bool $netgesconWorkflowMode = false;
public bool $onlyStraordinarieMode = false;
public bool $onlyRiscaldamentoMode = false;
public ?int $selectedStraordinaria = null;
public static function canAccess(): bool
{
return ModuleVisibility::canAccessGesconInternal(Auth::user());
}
public ?string $mastrinoTabella = null;
public ?string $mastrinoCodSpe = null;
public array $mastrinoRows = [];
public array $acquaRipartoRows = [];
public array $preventivoEditorRows = [];
public array $preventivoSummaryRows = [];
public array $preventivoTabellaOptions = [];
public array $ripartoPreviewColumns = [];
public array $ripartoPreviewRows = [];
public array $ripartoRateMonths = [];
public array $ripartoFocusBreakdown = [];
public array $personeCollegateAudit = [];
public array $preventivoGestioneMeta = [];
public ?int $ripartoFocusUnitId = null;
public function mount(): void
{
$user = Auth::user();
if ($user instanceof User && $this->filterAnno === null) {
$this->filterAnno = AnnoGestioneContext::resolveActiveAnno($user);
}
$tab = request()->query('tab');
if (in_array($tab, ['operazioni', 'preventivo', 'riparto', 'persone', 'consuntivo', 'straordinarie', 'acqua', 'incassi', 'fatture'], true)) {
$this->viewTab = $tab;
}
if ($this->onlyStraordinarieMode) {
$this->sectionKey = 'straordinarie';
$straord = request()->query('straord');
if (is_numeric($straord)) {
$this->selectedStraordinaria = (int) $straord;
}
if (! in_array($this->viewTab, ['operazioni', 'consuntivo'], true)) {
$this->viewTab = 'operazioni';
}
}
if ($this->onlyRiscaldamentoMode) {
$this->sectionKey = 'riscaldamento';
}
if ($this->netgesconWorkflowMode) {
$this->sortField = 'dt_spe';
$this->sortDirection = 'asc';
}
$gestioneFilter = request()->query('gestione_filter');
if (is_string($gestioneFilter) && in_array($gestioneFilter, ['all', 'O', 'R', 'S'], true)) {
$this->operazioniGestioneFilter = $gestioneFilter;
}
$this->hydratePreventivoWorkspace();
}
public function updatedFilterAnno(): void
{
$this->hydratePreventivoWorkspace();
}
public function updatedOperazioniGestioneFilter(): void
{
$this->resetPage();
}
public function updatedRipartoFocusUnitId(): void
{
$this->applyFocusRipartoBreakdown();
}
public function savePreventivoOrdinario(): void
{
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
$gestioneId = (int) ($this->preventivoGestioneMeta['id'] ?? 0);
if (! $activeStabileId || ! $gestioneId || ! Schema::hasTable('voci_spesa')) {
Notification::make()
->title('Preventivo non disponibile')
->body('Manca una gestione ordinaria attiva o la struttura dati delle voci spesa.')
->danger()
->send();
return;
}
DB::transaction(function (): void {
foreach ($this->preventivoEditorRows as $row) {
$payload = [
'importo_default' => is_numeric($row['importo_default'] ?? null) ? round((float) $row['importo_default'], 2) : 0.0,
'importo_consuntivo' => is_numeric($row['importo_consuntivo'] ?? null) ? round((float) $row['importo_consuntivo'], 2) : 0.0,
'percentuale_inquilino' => is_numeric($row['percentuale_inquilino'] ?? null) ? round((float) $row['percentuale_inquilino'], 2) : 0.0,
'percentuale_condomino' => max(0, 100 - (is_numeric($row['percentuale_inquilino'] ?? null) ? round((float) $row['percentuale_inquilino'], 2) : 0.0)),
'tabella_millesimale_default_id' => ! empty($row['tabella_millesimale_default_id']) ? (int) $row['tabella_millesimale_default_id'] : null,
];
DB::table('voci_spesa')
->where('id', (int) ($row['id'] ?? 0))
->where('stabile_id', (int) $activeStabileId)
->where('gestione_contabile_id', $gestioneId)
->update($payload);
}
});
$this->hydratePreventivoWorkspace();
Notification::make()
->title('Preventivo aggiornato')
->body('Le voci della gestione ordinaria sono state salvate e il riparto è stato ricalcolato.')
->success()
->send();
}
protected function hydratePreventivoWorkspace(): void
{
$this->preventivoEditorRows = [];
$this->preventivoSummaryRows = [];
$this->preventivoTabellaOptions = [];
$this->ripartoPreviewColumns = [];
$this->ripartoPreviewRows = [];
$this->ripartoRateMonths = [];
$this->ripartoFocusBreakdown = [];
$this->personeCollegateAudit = [];
$this->preventivoGestioneMeta = [];
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
if (! $activeStabileId || ! $this->filterAnno) {
return;
}
$tipo = 'ordinaria';
if ($this->onlyStraordinarieMode ?? false) {
$tipo = 'straordinaria';
} elseif ($this->onlyRiscaldamentoMode ?? false) {
$tipo = 'riscaldamento';
}
$service = app(PreventivoOrdinarioPreviewService::class);
$payload = $service->build((int) $activeStabileId, (int) $this->filterAnno, null, $tipo);
$this->preventivoGestioneMeta = $payload['gestione'] ?? [];
$this->preventivoEditorRows = $payload['preventivo_rows'] ?? [];
$this->preventivoSummaryRows = $payload['preventivo_summary'] ?? [];
$this->preventivoTabellaOptions = $payload['tabella_options'] ?? [];
$this->ripartoPreviewColumns = $payload['riparto_columns'] ?? [];
$this->ripartoPreviewRows = $payload['riparto_rows'] ?? [];
$this->ripartoRateMonths = $payload['mesi_rate'] ?? [];
$this->personeCollegateAudit = $payload['people_audit'] ?? [];
$focusId = $payload['focus_unit_id'] ?? null;
if ($this->ripartoFocusUnitId === null || ! collect($this->ripartoPreviewRows)->contains(fn(array $row): bool => (int) $row['unit_id'] === (int) $this->ripartoFocusUnitId)) {
$this->ripartoFocusUnitId = $focusId ? (int) $focusId : null;
}
$this->applyFocusRipartoBreakdown($payload['focus_breakdown'] ?? []);
}
protected function applyFocusRipartoBreakdown(array $fallback = []): void
{
if ($this->ripartoFocusUnitId === null) {
$this->ripartoFocusBreakdown = [];
return;
}
if ($fallback !== []) {
$this->ripartoFocusBreakdown = $fallback;
return;
}
$row = collect($this->ripartoPreviewRows)->firstWhere('unit_id', (int) $this->ripartoFocusUnitId);
if (! is_array($row)) {
$this->ripartoFocusBreakdown = [];
return;
}
$subjects = [];
$owners = $this->expandRipartoSubjects($row, 'owners');
$tenants = $this->expandRipartoSubjects($row, 'tenants');
foreach ($owners as $owner) {
$subjects[] = $owner;
}
foreach ($tenants as $tenant) {
$subjects[] = $tenant;
}
$this->ripartoFocusBreakdown = $subjects;
}
protected function expandRipartoSubjects(array $row, string $bucket): array
{
$source = $bucket === 'owners' ? ($row['owners'] ?? []) : ($row['tenants'] ?? []);
$pieces = [];
if (is_array($source) && $source !== []) {
foreach ($source as $item) {
$pieces[] = [
'name' => (string) ($item['name'] ?? 'Soggetto'),
'quota' => is_numeric($item['quota'] ?? null) ? (float) $item['quota'] : null,
];
}
}
if ($pieces === []) {
$summary = (string) ($row['people_summary'] ?? '');
$names = $bucket === 'owners'
? preg_split('/\s*,\s*/', preg_replace('/^Prop:\s*/', '', preg_match('/Prop:\s*([^·]+)/', $summary, $m) ? $m[1] : ''), -1, PREG_SPLIT_NO_EMPTY)
: preg_split('/\s*,\s*/', preg_replace('/^Inq:\s*/', '', preg_match('/Inq:\s*(.+)$/', $summary, $m) ? $m[1] : ''), -1, PREG_SPLIT_NO_EMPTY);
$pieces = array_map(fn(string $name): array=> ['name' => $name, 'quota' => null], $names ?: []);
}
if ($pieces === []) {
return [];
}
$count = count($pieces);
$declared = collect($pieces)->sum(fn(array $item): float => is_numeric($item['quota'] ?? null) ? (float) $item['quota'] : 0.0);
$baseKey = $bucket === 'owners' ? 'totale_condomini' : 'totale_inquilini';
$rows = [];
foreach ($pieces as $item) {
$share = $declared > 0 ? (((float) ($item['quota'] ?? 0.0)) / $declared) : (1 / max($count, 1));
$columns = [];
$totale = 0.0;
foreach (($row['table_breakdown'] ?? []) as $code => $values) {
$base = (float) ($values[$bucket === 'owners' ? 'condomini' : 'inquilini'] ?? 0.0);
$quota = $base * $share;
$columns[$code] = $quota;
$totale += $quota;
}
$rows[] = [
'name' => $item['name'],
'role' => $bucket === 'owners' ? 'Condomino' : 'Inquilino',
'quota_percent' => $share * 100,
'columns' => $columns,
'totale' => $totale > 0 ? $totale : ((float) ($row[$baseKey] ?? 0.0) * $share),
];
}
return $rows;
}
public function getStraordinarieGestioniProperty(): array
{
if (! $this->onlyStraordinarieMode) {
return [];
}
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
return [];
}
$query = DB::connection('gescon_import')
->table('operazioni')
->where('gestione', 'S')
->whereNotNull('n_stra');
$legacyCode = $this->resolveLegacyCodiceStabile();
if ($legacyCode && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
$query->where('cod_stabile', $legacyCode);
}
if ($this->filterAnno) {
$query->whereYear('dt_spe', $this->filterAnno);
}
$rows = $query
->selectRaw('n_stra, MIN(dt_spe) as data_inizio, MAX(dt_spe) as data_fine, COUNT(*) as totale')
->groupBy('n_stra')
->orderBy('n_stra')
->get();
$stabileId = StabileContext::resolveActiveStabileId(Auth::user());
$lavoriNames = [];
if ($stabileId && Schema::hasTable('lavori_straordinari') && Schema::hasTable('gestioni_contabili')) {
$lavoriNames = DB::table('lavori_straordinari')
->join('gestioni_contabili', 'gestioni_contabili.id', '=', 'lavori_straordinari.gestione_id')
->where('gestioni_contabili.stabile_id', $stabileId)
->pluck('lavori_straordinari.descrizione', 'gestioni_contabili.numero_straordinaria')
->all();
}
$out = [];
foreach ($rows as $row) {
$nStra = is_numeric($row->n_stra ?? null) ? (int) $row->n_stra : null;
if (! $nStra) {
continue;
}
$periodo = null;
if (! empty($row->data_inizio) || ! empty($row->data_fine)) {
$start = ! empty($row->data_inizio) ? (string) \Carbon\Carbon::parse($row->data_inizio)->format('d/m/Y') : '—';
$end = ! empty($row->data_fine) ? (string) \Carbon\Carbon::parse($row->data_fine)->format('d/m/Y') : '—';
$periodo = $start . ' → ' . $end;
}
$desc = $lavoriNames[$nStra] ?? null;
$label = $desc ? 'Straordinaria #' . $nStra . ' ' . $desc : 'Gestione straordinaria #' . $nStra;
$out[] = [
'n_stra' => $nStra,
'label' => $label,
'periodo' => $periodo,
'totale' => (int) ($row->totale ?? 0),
];
}
if ($this->selectedStraordinaria === null && ! empty($out)) {
$this->selectedStraordinaria = (int) ($out[0]['n_stra'] ?? null);
}
return $out;
}
private function resolveLegacyCodiceStabile(): ?string
{
$user = Auth::user();
$stabile = StabileContext::getActiveStabile($user);
return StabileContext::preferredLegacyCode($stabile);
}
private function resolveLegacyYearForRiparto(?string $codStabile): ?string
{
if (! $codStabile) {
return null;
}
return (string) (DB::connection('gescon_import')
->table('dett_tab')
->where('cod_stabile', $codStabile)
->max('legacy_year') ?: '');
}
private function getLegacyAnnualMetaMap(?string $codStabile): array
{
if (! $codStabile || ! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
return [];
}
return DB::connection('gescon_import')
->table('gestioni_annuali')
->where('cod_stabile', $codStabile)
->orderBy('cartella')
->get([
'cartella',
'anno_ordinario',
'anno_riscaldamento',
'descrizione',
'ordinaria_dal',
'ordinaria_al',
'riscaldamento_dal',
'riscaldamento_al',
])
->keyBy(fn($row) => trim((string) ($row->cartella ?? '')))
->all();
}
private function extractLegacyYearInt(?string $value): ?int
{
$value = trim((string) ($value ?? ''));
if ($value === '') {
return null;
}
if (preg_match('/(19|20)\d{2}/', $value, $match) === 1) {
return (int) $match[0];
}
return is_numeric($value) && strlen($value) === 4 ? (int) $value : null;
}
private function formatLegacyPeriod(?string $from, ?string $to): ?string
{
if (! $from && ! $to) {
return null;
}
$start = $from ? (string) \Carbon\Carbon::parse($from)->format('d/m/Y') : '—';
$end = $to ? (string) \Carbon\Carbon::parse($to)->format('d/m/Y') : '—';
return $start . ' → ' . $end;
}
private function resolveLegacyRowMeta(object $row, array $legacyAnnualMap): array
{
$cartella = trim((string) ($row->legacy_year ?? ''));
$tipo = strtoupper(trim((string) ($row->gestione ?? 'O')));
$annual = $cartella !== '' ? ($legacyAnnualMap[$cartella] ?? null) : null;
$isRiscaldamento = $tipo === 'R';
$annoLabel = trim((string) ($isRiscaldamento ? ($annual->anno_riscaldamento ?? '') : ($annual->anno_ordinario ?? '')));
$periodo = $this->formatLegacyPeriod(
$isRiscaldamento ? ($annual->riscaldamento_dal ?? $annual->ordinaria_dal ?? null) : ($annual->ordinaria_dal ?? null),
$isRiscaldamento ? ($annual->riscaldamento_al ?? $annual->ordinaria_al ?? null) : ($annual->ordinaria_al ?? null)
);
return [
'cartella' => $cartella !== '' ? $cartella : null,
'anno_label' => $annoLabel !== '' ? $annoLabel : null,
'anno_gestione' => $this->extractLegacyYearInt($annoLabel),
'periodo' => $periodo,
'tipo_label' => match ($tipo) {
'R' => 'Riscaldamento',
'S' => 'Straordinaria' . (! empty($row->n_stra) ? ' #' . (int) $row->n_stra : ''),
default => 'Ordinaria',
},
];
}
public function getLegacyOperazioniScopedByStabileProperty(): bool
{
return Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile');
}
public function getLegacyGestioneLabelProperty(): ?string
{
$legacyCode = $this->resolveLegacyCodiceStabile();
if (! $legacyCode || ! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
return null;
}
$rows = collect($this->getLegacyAnnualMetaMap($legacyCode));
if ($this->filterAnno) {
$rows = $rows->filter(function ($row): bool {
return $this->extractLegacyYearInt((string) ($row->anno_ordinario ?? '')) === (int) $this->filterAnno
|| $this->extractLegacyYearInt((string) ($row->anno_riscaldamento ?? '')) === (int) $this->filterAnno;
});
}
$row = $rows->sortByDesc('cartella')->first();
if (! $row) {
return null;
}
$dir = trim((string) ($row->cartella ?? ''));
$anno = trim((string) ($row->anno_ordinario ?? ''));
if ($dir === '' && $anno === '') {
return null;
}
$dirLabel = $dir !== '' ? $dir : '—';
$annoLabel = $anno !== '' ? $anno : '—';
return 'Gestione ' . $dirLabel . ' → ' . $annoLabel;
}
public function getLegacyGestioneAnnoProperty(): ?int
{
if ($this->filterAnno) {
return (int) $this->filterAnno;
}
$legacyCode = $this->resolveLegacyCodiceStabile();
if (! $legacyCode || ! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
return null;
}
$row = collect($this->getLegacyAnnualMetaMap($legacyCode))
->sortByDesc('cartella')
->first();
return $row ? $this->extractLegacyYearInt((string) ($row->anno_ordinario ?? '')) : null;
}
public function getLegacyGestioniHubProperty(): array
{
$legacyCode = $this->resolveLegacyCodiceStabile();
if (! $legacyCode || ! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
return [];
}
$legacyAnnualMap = $this->getLegacyAnnualMetaMap($legacyCode);
if ($legacyAnnualMap === []) {
return [];
}
$totals = collect();
if (Schema::connection('gescon_import')->hasTable('operazioni')) {
$totals = DB::connection('gescon_import')
->table('operazioni')
->where('cod_stabile', $legacyCode)
->select('legacy_year', 'gestione')
->selectRaw('COUNT(*) as totale_righe')
->selectRaw('SUM(COALESCE(importo_spese, 0)) as totale_spese')
->selectRaw('SUM(COALESCE(importo_entrate, 0)) as totale_entrate')
->selectRaw('SUM(COALESCE(importo_debiti, 0)) as totale_debiti')
->selectRaw('SUM(COALESCE(importo_crediti, 0)) as totale_crediti')
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale_lordo')
->groupBy('legacy_year', 'gestione')
->get()
->groupBy(fn($row) => trim((string) ($row->legacy_year ?? '')));
}
$rows = [];
foreach ($legacyAnnualMap as $cartella => $meta) {
$group = collect($totals->get($cartella, []))->keyBy(fn($row) => strtoupper(trim((string) ($row->gestione ?? 'O'))));
if ($this->filterAnno) {
$annoOrd = $this->extractLegacyYearInt((string) ($meta->anno_ordinario ?? ''));
$annoRisc = $this->extractLegacyYearInt((string) ($meta->anno_riscaldamento ?? ''));
if ($annoOrd !== (int) $this->filterAnno && $annoRisc !== (int) $this->filterAnno) {
continue;
}
}
$rows[] = [
'cartella' => $cartella,
'anno_ordinario' => trim((string) ($meta->anno_ordinario ?? '')) ?: '—',
'periodo_ordinario' => $this->formatLegacyPeriod($meta->ordinaria_dal ?? null, $meta->ordinaria_al ?? null) ?: '—',
'anno_riscaldamento' => trim((string) ($meta->anno_riscaldamento ?? '')) ?: '—',
'periodo_riscaldamento' => $this->formatLegacyPeriod($meta->riscaldamento_dal ?? null, $meta->riscaldamento_al ?? null) ?: '—',
'ordinaria' => $group->get('O'),
'riscaldamento' => $group->get('R'),
'straordinaria' => $group->get('S'),
];
}
return $rows;
}
public function getAcquaSummaryProperty(): array
{
$legacyCode = $this->resolveLegacyCodiceStabile();
$legacyYear = $this->resolveLegacyYearForRiparto($legacyCode);
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
return [];
}
$rowsQuery = DB::connection('gescon_import')
->table('operazioni')
->where('gestione', 'O')
->whereIn('cod_spe', ['AC1', 'AC2']);
if ($legacyCode && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
$rowsQuery->where('cod_stabile', $legacyCode);
}
$rows = $rowsQuery
->select('cod_spe')
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale')
->groupBy('cod_spe')
->get();
$byCode = [];
foreach ($rows as $r) {
$byCode[(string) $r->cod_spe] = (float) ($r->totale ?? 0);
}
$riparto = DB::connection('gescon_import')
->table('dett_tab')
->where('cod_tab', 'ACQUA')
->when($legacyCode, fn($q) => $q->where('cod_stabile', $legacyCode))
->when($legacyYear, fn($q) => $q->where('legacy_year', $legacyYear))
->sum(DB::raw('COALESCE(prev_euro, 0)'));
$totaleFatture = array_sum($byCode);
return [
'voci' => $byCode,
'totale' => $totaleFatture,
'riparto_totale' => (float) $riparto,
'riparto_delta' => (float) ($riparto - $totaleFatture),
];
}
public function getAcquaRipartoProperty(): array
{
$legacyCode = $this->resolveLegacyCodiceStabile();
$legacyYear = $this->resolveLegacyYearForRiparto($legacyCode);
if (! $legacyCode) {
return ['rows' => [], 'totale' => 0.0];
}
$condQuery = DB::connection('gescon_import')
->table('condomin')
->where('cod_stabile', $legacyCode);
if ($legacyYear) {
$condQuery->where('legacy_year', $legacyYear);
}
$condominCols = Schema::connection('gescon_import')->getColumnListing('condomin');
$desired = [
'id_cond',
'scala',
'interno',
'piano',
'cognome',
'nome',
'proprietario_denominazione',
'inquilino_denominazione',
'inquil_nome',
];
$actual = array_intersect($desired, $condominCols);
$condRows = $condQuery
->get($actual)
->keyBy('id_cond');
$ripartoQuery = DB::connection('gescon_import')
->table('dett_tab')
->where('cod_tab', 'ACQUA')
->where('cod_stabile', $legacyCode);
if ($legacyYear) {
$ripartoQuery->where('legacy_year', $legacyYear);
}
$ripartoRows = $ripartoQuery
->orderBy('id_cond')
->orderBy('cond_inquil')
->get(['id_cond', 'cond_inquil', 'prev_euro']);
$out = [];
$totale = 0.0;
foreach ($ripartoRows as $r) {
$cond = $condRows[$r->id_cond] ?? null;
$ruolo = strtoupper((string) ($r->cond_inquil ?? ''));
$nominativo = '';
if ($cond) {
if ($ruolo === 'I') {
$nominativo = trim((string) ($cond->inquilino_denominazione ?? ''));
if ($nominativo === '') {
$nominativo = trim((string) ($cond->inquil_nome ?? ''));
}
} else {
$nominativo = trim((string) ($cond->proprietario_denominazione ?? ''));
if ($nominativo === '') {
$nominativo = trim((string) (($cond->cognome ?? '') . ' ' . ($cond->nome ?? '')));
}
}
}
if ($ruolo === 'I' && $nominativo === '') {
continue;
}
if ($nominativo === '') {
$nominativo = '—';
}
$val = (float) ($r->prev_euro ?? 0);
$totale += $val;
$out[] = [
'id_cond' => $r->id_cond,
'ruolo' => $ruolo !== '' ? $ruolo : 'C',
'nominativo' => $nominativo,
'scala' => $cond->scala ?? null,
'interno' => $cond->interno ?? null,
'piano' => $cond->piano ?? null,
'prev_euro' => $val,
];
}
usort($out, function (array $left, array $right): int {
return strnatcasecmp($this->buildAcquaRipartoSortKey($left), $this->buildAcquaRipartoSortKey($right));
});
return ['rows' => $out, 'totale' => $totale];
}
private function buildAcquaRipartoSortKey(array $row): string
{
return implode('|', [
$this->normalizeAcquaNaturalToken((string) ($row['scala'] ?? '')),
$this->normalizeAcquaNaturalToken((string) ($row['interno'] ?? '')),
$this->normalizeAcquaNaturalToken((string) ($row['piano'] ?? '')),
$this->normalizeAcquaNaturalToken((string) ($row['nominativo'] ?? '')),
str_pad((string) ((int) ($row['id_cond'] ?? 0)), 10, '0', STR_PAD_LEFT),
]);
}
private function normalizeAcquaNaturalToken(string $value): string
{
$normalized = strtoupper(trim($value));
return preg_replace('/[\s\.\-\/]+/', '', $normalized) ?? '';
}
public ?int $dettPersNSpe = null;
public array $dettPersRows = [];
public string $dettPersSearch = '';
public float $dettPersTotale = 0.0;
public float $dettPersImportoOperazione = 0.0;
public float $dettPersDelta = 0.0;
public bool $dettPersQuadraturaOk = true;
public string $dettPersRipartoMode = 'legacy';
public ?string $dettPersRipartoTabella = null;
public ?string $dettPersRipartoTarget = null;
public array $dettPersRipartoTabellaOptions = [];
public array $dettPersRipartoTargetOptions = [];
public array $dettPersRecipients = [];
public array $dettPersLegacyAmounts = [];
public ?string $dettPersLegacyCode = null;
public ?string $dettPersLegacyYear = null;
public array $dettPersLegacyUnico = [];
public function getOperazioniProperty()
{
$base = $this->buildOperazioniQuery();
$paginator = $this->applyOperazioniSorting($base)
->paginate($this->perPage);
$items = $paginator->getCollection();
$legacyAnnualMap = $this->getLegacyAnnualMetaMap($this->resolveLegacyCodiceStabile());
if ($this->netgesconWorkflowMode) {
foreach ($items as $row) {
$voceSnap = !empty($row->voce_spesa_snapshot) ? json_decode($row->voce_spesa_snapshot) : null;
$fornSnap = !empty($row->fornitore_snapshot) ? json_decode($row->fornitore_snapshot) : null;
$row->cod_spe = $voceSnap->legacy_codice ?? $voceSnap->codice ?? $row->cod_spe ?? null;
$row->voce_descrizione = $voceSnap->descrizione ?? null;
$row->cod_for = $fornSnap->cod_forn ?? $fornSnap->old_id ?? null;
$row->fornitore_nome = $fornSnap->ragione_sociale ?? trim(($fornSnap->nome ?? '') . ' ' . ($fornSnap->cognome ?? '')) ?: null;
$row->tabella = $voceSnap->legacy_tabella ?? null;
}
}
$rdaByCode = [];
foreach ($items as $row) {
$rdaCode = null;
if (! empty($row->rif_rda)) {
$rdaCode = $row->rif_rda;
} elseif (is_string($row->benef ?? null) && str_starts_with(strtoupper(trim((string) $row->benef)), 'RDA:')) {
$rdaCode = trim(substr(trim((string) $row->benef), 4));
}
if ($rdaCode) {
$benef = trim((string) ($row->benef ?? ''));
$isRdaLine = $benef !== '' && str_starts_with(strtoupper($benef), 'RDA:');
if ($isRdaLine) {
$rdaByCode[$rdaCode] = [
'importo' => $this->computeSignedAmount($row),
'data' => $row->dt_spe ?? null,
];
}
}
}
$codSpe = $items->pluck('cod_spe')->filter()->unique()->values()->all();
$legacyCode = $this->resolveLegacyCodiceStabile();
$legacyYears = $items
->pluck('legacy_year')
->map(fn($value) => trim((string) $value))
->filter()
->unique()
->values()
->all();
$codFor = $items
->pluck('cod_for')
->filter()
->map(fn($v) => trim((string) $v))
->filter()
->unique()
->values()
->all();
$codForNoZero = array_values(array_filter(array_unique(array_map(fn($v) => ltrim((string) $v, '0'), $codFor))));
$codFor = array_values(array_unique(array_merge($codFor, $codForNoZero)));
$codTab = $items->pluck('tabella')->filter()->unique()->values()->all();
$vociMap = [];
$vociTabellaMap = [];
if (! empty($codSpe)) {
$vociRows = DB::connection('gescon_import')
->table('voc_spe')
->whereIn('cod', $codSpe)
->when($legacyCode && Schema::connection('gescon_import')->hasColumn('voc_spe', 'cod_stabile'), fn($q) => $q->where('cod_stabile', $legacyCode))
->when($legacyYears !== [] && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year'), fn($q) => $q->whereIn('legacy_year', $legacyYears))
->get(['cod', 'descriz', 'tabella', 'legacy_year']);
foreach ($vociRows as $vocRow) {
$code = trim((string) ($vocRow->cod ?? ''));
$year = trim((string) ($vocRow->legacy_year ?? ''));
if ($code === '') {
continue;
}
if ($year !== '') {
$vociMap[$year . '|' . $code] = $vocRow->descriz;
$vociTabellaMap[$year . '|' . $code] = $vocRow->tabella;
}
$vociMap[$code] ??= $vocRow->descriz;
$vociTabellaMap[$code] ??= $vocRow->tabella;
}
}
$tabFromVoci = array_values(array_filter(array_unique(array_map('strval', $vociTabellaMap))));
if (! empty($tabFromVoci)) {
$codTab = array_values(array_unique(array_merge($codTab, $tabFromVoci)));
}
$tabelleMap = [];
if (! empty($codTab)) {
$tabelleMap = DB::connection('gescon_import')
->table('tabelle_millesimali')
->whereIn('cod_tabella', $codTab)
->pluck('denominazione', 'cod_tabella')
->all();
}
$fornitoriMap = [];
$fornitoriIdMap = [];
$codFornToLegacyId = [];
$legacyIds = [];
if (! empty($codFor)) {
$useStg = Schema::hasTable('stg_fornitori_gescon');
if ($useStg) {
$legacyRows = DB::table('stg_fornitori_gescon')
->whereIn('cod_forn', $codFor)
->get(['cod_forn', 'legacy_id_fornitore', 'denominazione', 'cognome', 'nome']);
} else {
$stagingConn = DB::connection('gescon_import');
$tableName = $stagingConn->getSchemaBuilder()->hasTable('mdb_fornitori') ? 'mdb_fornitori' : 'fornitori';
$hasIdForn = $stagingConn->getSchemaBuilder()->hasColumn($tableName, 'id_fornitore');
$hasDenom = $stagingConn->getSchemaBuilder()->hasColumn($tableName, 'denominazione');
$hasRagione = $stagingConn->getSchemaBuilder()->hasColumn($tableName, 'ragione_sociale');
$selectFields = ['cod_forn'];
if ($hasIdForn) {
$selectFields[] = 'id_fornitore';
} else {
$selectFields[] = 'id as id_fornitore';
}
if ($hasDenom) {
$selectFields[] = 'denominazione';
} elseif ($hasRagione) {
$selectFields[] = 'ragione_sociale as denominazione';
}
if ($stagingConn->getSchemaBuilder()->hasColumn($tableName, 'cognome')) {
$selectFields[] = 'cognome';
}
if ($stagingConn->getSchemaBuilder()->hasColumn($tableName, 'nome')) {
$selectFields[] = 'nome';
}
$legacyRows = $stagingConn->table($tableName)
->whereIn('cod_forn', $codFor)
->get($selectFields);
}
$fornitoriMap = $legacyRows
->mapWithKeys(function ($f) {
$label = $f->denominazione ?: trim(($f->nome ?? '') . ' ' . ($f->cognome ?? ''));
return [trim((string) $f->cod_forn) => $label];
})
->all();
$codFornToLegacyId = $legacyRows
->mapWithKeys(fn($f) => [
trim((string) $f->cod_forn) => $useStg ? $f->cod_forn : ($f->id_fornitore ?? null),
])
->all();
foreach (array_keys($fornitoriMap) as $code) {
$trimmed = trim((string) $code);
$noZero = ltrim($trimmed, '0');
if ($trimmed !== '' && ! isset($fornitoriMap[$trimmed])) {
$fornitoriMap[$trimmed] = $fornitoriMap[$code];
}
if ($noZero !== '' && ! isset($fornitoriMap[$noZero])) {
$fornitoriMap[$noZero] = $fornitoriMap[$code];
}
if ($trimmed !== '' && ! isset($codFornToLegacyId[$trimmed]) && isset($codFornToLegacyId[$code])) {
$codFornToLegacyId[$trimmed] = $codFornToLegacyId[$code];
}
if ($noZero !== '' && ! isset($codFornToLegacyId[$noZero]) && isset($codFornToLegacyId[$code])) {
$codFornToLegacyId[$noZero] = $codFornToLegacyId[$code];
}
}
$missingCodes = [];
foreach ($codFor as $c) {
if (empty($fornitoriMap[$c])) {
$missingCodes[] = $c;
}
}
if (! empty($missingCodes)) {
$prodRows = DB::table('fornitori')
->whereIn('old_id', $missingCodes)
->get(['id', 'old_id', 'ragione_sociale', 'nome', 'cognome']);
foreach ($prodRows as $pr) {
$label = $pr->ragione_sociale ?: trim(($pr->nome ?? '') . ' ' . ($pr->cognome ?? ''));
if ($label !== '') {
$fornitoriMap[trim((string) $pr->old_id)] = $label;
$codFornToLegacyId[trim((string) $pr->old_id)] = $pr->old_id;
if (! isset($fornitoriIdMap[$pr->old_id])) {
$fornitoriIdMap[$pr->old_id] = $pr->id;
}
}
}
}
$legacyIds = $legacyRows
->map(fn($f) => $useStg ? $f->cod_forn : ($f->id_fornitore ?? null))
->filter()
->unique()
->values()
->all();
}
$activeStabileId = null;
$authUser = Auth::user();
if ($authUser) {
$activeStabileId = StabileContext::resolveActiveStabileId($authUser);
}
if (! empty($legacyIds) && Schema::hasTable('fornitori') && $this->netgesconWorkflowMode) {
$activeAmministratoreId = null;
if ($activeStabileId && Schema::hasTable('stabili')) {
$activeAmministratoreId = DB::table('stabili')
->where('id', (int) $activeStabileId)
->value('amministratore_id');
}
$fornitoriRows = DB::table('fornitori')
->whereIn('old_id', $legacyIds)
->get(['id', 'old_id', 'amministratore_id']);
$fornitoriByOldId = $fornitoriRows
->groupBy('old_id')
->map(function ($rows) use ($activeAmministratoreId) {
if ($activeAmministratoreId) {
$match = $rows->first(fn($row) => (int) ($row->amministratore_id ?? 0) === (int) $activeAmministratoreId);
if ($match) {
return (int) $match->id;
}
}
return (int) ($rows->first()->id ?? 0);
})
->filter(fn($value) => $value > 0)
->all();
$fornitoriIdMap = $fornitoriByOldId;
} elseif (! empty($legacyIds)) {
$fornitoriIdMap = DB::table('fornitori')
->whereIn('old_id', $legacyIds)
->pluck('id', 'old_id')
->all();
}
$fattureByFornitore = [];
$fattureByFornitoreDocumento = [];
$fattureByDocumentoGlobale = [];
$fornitoriLocaliById = [];
$vociLocaliByCode = [];
$fornitoriIds = array_values(array_filter($fornitoriIdMap));
if (! empty($fornitoriIds) && Schema::hasTable('fornitori')) {
$fornitoriRows = DB::table('fornitori')
->whereIn('id', $fornitoriIds)
->get(['id', 'ragione_sociale', 'nome', 'cognome']);
foreach ($fornitoriRows as $fornitoreRow) {
$label = trim((string) ($fornitoreRow->ragione_sociale ?? ''));
if ($label === '') {
$label = trim((string) (($fornitoreRow->nome ?? '') . ' ' . ($fornitoreRow->cognome ?? '')));
}
$fornitoriLocaliById[(int) $fornitoreRow->id] = $label !== '' ? $label : null;
}
}
if ($activeStabileId && Schema::hasTable('voci_spesa')) {
$activeYear = $this->getLegacyGestioneAnnoProperty();
$query = DB::table('voci_spesa')
->where('stabile_id', (int) $activeStabileId);
if ($activeYear) {
$gestioneIds = DB::table('gestioni_contabili')
->where('stabile_id', (int) $activeStabileId)
->where('anno_gestione', $activeYear)
->pluck('id')
->all();
if (! empty($gestioneIds)) {
$query->whereIn('gestione_contabile_id', $gestioneIds);
}
}
$vociRows = $query->get(['codice', 'legacy_codice', 'descrizione', 'tabella_millesimale_default_id']);
$tabellaIds = $vociRows
->pluck('tabella_millesimale_default_id')
->filter(fn($value) => is_numeric($value))
->map(fn($value) => (int) $value)
->unique()
->values()
->all();
$tabellaLocaleMap = [];
if (! empty($tabellaIds) && Schema::hasTable('tabelle_millesimali')) {
$tabellaLocaleMap = DB::table('tabelle_millesimali')
->whereIn('id', $tabellaIds)
->get(['id', 'codice_tabella', 'nome_tabella'])
->mapWithKeys(function ($tabellaRow) {
$name = trim((string) ($tabellaRow->nome_tabella ?? ''));
$code = trim((string) ($tabellaRow->codice_tabella ?? ''));
return [(int) $tabellaRow->id => ($name !== '' ? $name : $code)];
})
->all();
}
foreach ($vociRows as $voceRow) {
$codes = array_filter([
strtoupper(trim((string) ($voceRow->codice ?? ''))),
strtoupper(trim((string) ($voceRow->legacy_codice ?? ''))),
]);
$voceData = [
'descrizione' => trim((string) ($voceRow->descrizione ?? '')),
'tabella' => $tabellaLocaleMap[(int) ($voceRow->tabella_millesimale_default_id ?? 0)] ?? null,
];
foreach ($codes as $code) {
$vociLocaliByCode[$code] = $voceData;
}
}
}
if (! empty($fornitoriIds) && DB::getSchemaBuilder()->hasTable('contabilita_fatture_fornitori')) {
$fatture = DB::table('contabilita_fatture_fornitori')
->when($activeStabileId, fn($q) => $q->where('stabile_id', (int) $activeStabileId))
->whereIn('fornitore_id', $fornitoriIds)
->select([
'id',
'fornitore_id',
'numero_documento',
'data_documento',
'totale',
'netto_da_pagare',
'data_pagamento',
'ritenuta_importo',
'data_versamento_ra',
'stato',
])
->get();
foreach ($fatture as $f) {
$fattureByFornitore[$f->fornitore_id][] = $f;
$docNumber = $this->normalizeInvoiceNumber($f->numero_documento ?? null);
$docDate = ! empty($f->data_documento) ? (string) \Carbon\Carbon::parse($f->data_documento)->format('Y-m-d') : null;
if ($docNumber !== null && $docDate !== null) {
$key = (int) $f->fornitore_id . '|' . $docNumber . '|' . $docDate;
$fattureByFornitoreDocumento[$key][] = $f;
$globalKey = $docNumber . '|' . $docDate;
$fattureByDocumentoGlobale[$globalKey][] = $f;
}
}
}
$mapped = $items->map(function ($row) use ($vociMap, $vociTabellaMap, $tabelleMap, $fornitoriMap, $fornitoriIdMap, $fattureByFornitore, $fattureByFornitoreDocumento, $fattureByDocumentoGlobale, $fornitoriLocaliById, $vociLocaliByCode, $codFornToLegacyId, $rdaByCode, $legacyAnnualMap) {
$legacyMeta = $this->resolveLegacyRowMeta($row, $legacyAnnualMap);
$row->legacy_cartella = $legacyMeta['cartella'];
$row->legacy_anno_label = $legacyMeta['anno_label'];
$row->legacy_periodo = $legacyMeta['periodo'];
$row->legacy_gestione_tipo = $legacyMeta['tipo_label'];
$voceKey = trim((string) ($row->legacy_year ?? '')) . '|' . trim((string) ($row->cod_spe ?? ''));
$row->voce_descrizione = $vociMap[$voceKey] ?? ($vociMap[$row->cod_spe ?? ''] ?? null);
if (empty($row->tabella) && ! empty($vociTabellaMap[$voceKey] ?? $vociTabellaMap[$row->cod_spe ?? ''] ?? null)) {
$row->tabella = $vociTabellaMap[$voceKey] ?? $vociTabellaMap[$row->cod_spe ?? ''];
}
$row->tabella_descrizione = $tabelleMap[$row->tabella ?? ''] ?? null;
$row->legacy_compet = strtoupper(trim((string) ($row->compet ?? 'C')));
$row->legacy_natura_label = trim((string) ($row->natura2 ?? '')) !== '' ? trim((string) $row->natura2) : '—';
$row->legacy_contabile_label = match (true) {
$row->legacy_compet === 'P' => 'Pagamento competenza precedente',
strtoupper((string) ($row->natura2 ?? '')) === 'DEBITO' => 'Da riportare come debito',
strtoupper((string) ($row->natura2 ?? '')) === 'CREDITO' => 'Credito',
in_array(strtoupper((string) ($row->natura2 ?? '')), ['ENTRATA', 'ENTRATE', 'E'], true) => 'Entrata',
default => 'Costo di competenza corrente',
};
$codFor = trim((string) ($row->cod_for ?? ''));
$codForNoZero = ltrim($codFor, '0');
$row->fornitore_nome = $fornitoriMap[$codFor] ?? ($codForNoZero !== '' ? ($fornitoriMap[$codForNoZero] ?? null) : null);
$row->fornitore_id = null;
if ($codFor !== '') {
$legacyId = $codFornToLegacyId[$codFor] ?? ($codForNoZero !== '' ? ($codFornToLegacyId[$codForNoZero] ?? null) : null);
if ($legacyId && isset($fornitoriIdMap[$legacyId])) {
$row->fornitore_id = (int) $fornitoriIdMap[$legacyId];
}
}
$row->fornitore_locale_nome = null;
$row->voce_spesa_locale = null;
$row->tabella_locale = null;
if ($this->netgesconWorkflowMode) {
$row->fornitore_locale_nome = $row->fornitore_id ? ($fornitoriLocaliById[$row->fornitore_id] ?? null) : null;
$voceLocale = $vociLocaliByCode[strtoupper((string) ($row->cod_spe ?? ''))] ?? null;
$row->voce_spesa_locale = $voceLocale['descrizione'] ?? null;
$row->tabella_locale = $voceLocale['tabella'] ?? null;
}
$row->rda_code = null;
$row->is_rda_line = false;
if (! empty($row->rif_rda)) {
$row->rda_code = $row->rif_rda;
} elseif (is_string($row->benef ?? null) && str_starts_with(strtoupper(trim((string) $row->benef)), 'RDA:')) {
$row->rda_code = trim(substr(trim((string) $row->benef), 4));
$row->is_rda_line = true;
}
$row->ra_importo = $row->netto_vers_rda ?? $row->importo_euro_770 ?? null;
$row->ra_data = null;
$row->fe_numero = null;
$row->fe_totale = null;
$row->fe_fattura_id = null;
$row->fe_ricevuta_id = null;
$row->fe_display = $row->fe_uid ?? null;
if ($row->ra_importo !== null) {
$raw = (string) $row->ra_importo;
$raw = str_replace(['.', ' '], ['', ''], $raw);
$raw = str_replace(',', '.', $raw);
$row->ra_importo = is_numeric($raw) ? (float) $raw : null;
}
$amount = (float) ($row->importo_euro ?? $row->importo ?? 0);
$row->importo_signed = $this->computeSignedAmount($row);
$row->rda_row_importo = null;
$row->rda_row_data = null;
if (! $row->is_rda_line && ! empty($row->rda_code) && isset($rdaByCode[$row->rda_code])) {
$row->rda_row_importo = $rdaByCode[$row->rda_code]['importo'] ?? null;
$row->rda_row_data = $rdaByCode[$row->rda_code]['data'] ?? null;
}
$fornId = $row->fornitore_id;
$best = null;
$docNumber = $this->normalizeInvoiceNumber($row->num_fat ?? null);
$docDate = ! empty($row->dt_fat) ? (string) \Carbon\Carbon::parse($row->dt_fat)->format('Y-m-d') : null;
if ($this->netgesconWorkflowMode && $fornId && $docNumber !== null && $docDate !== null) {
$strictKey = $fornId . '|' . $docNumber . '|' . $docDate;
if (! empty($fattureByFornitoreDocumento[$strictKey])) {
$best = $fattureByFornitoreDocumento[$strictKey][0];
}
}
if ($this->netgesconWorkflowMode && ! $best && $docNumber !== null && $docDate !== null) {
$globalKey = $docNumber . '|' . $docDate;
$globalMatches = $fattureByDocumentoGlobale[$globalKey] ?? [];
if (count($globalMatches) === 1) {
$best = $globalMatches[0];
}
}
if (! $best && $fornId && isset($fattureByFornitore[$fornId])) {
foreach ($fattureByFornitore[$fornId] as $f) {
$tot = (float) ($f->totale ?? 0);
$net = (float) ($f->netto_da_pagare ?? 0);
$matchTot = abs(abs($amount) - $tot) <= 0.01;
$matchNet = abs(abs($amount) - $net) <= 0.01;
if ($matchTot || $matchNet) {
$best = $f;
break;
}
}
}
if ($best) {
$row->fe_fattura_id = $best->id ?? null;
$row->fe_ricevuta_id = $best->fattura_elettronica_id ?? null;
$row->fe_numero = $best->numero_documento ?? null;
$row->fe_totale = $best->totale ?? $best->netto_da_pagare ?? null;
$row->fe_data = $best->data_documento ?? null;
$row->fe_netto = $best->netto_da_pagare ?? null;
$row->fe_stato = $best->stato ?? null;
$row->bonifico_data = $best->data_pagamento ?? null;
$row->ra_importo = $row->ra_importo ?? ($best->ritenuta_importo ?? null);
$row->ra_data = $best->data_versamento_ra ?? null;
}
if ($this->netgesconWorkflowMode && empty($row->bonifico_data) && ! empty($row->dt_spe)) {
$row->bonifico_data = $row->dt_spe;
}
$row->rda_importo_calcolata = null;
$row->rda_scadenza = null;
$row->rda_stato = null;
$row->protocollo_logico = null;
if ($this->netgesconWorkflowMode) {
$row->rda_importo_calcolata = $row->rda_row_importo ?? $row->ra_importo ?? null;
$row->rda_scadenza = $this->computeRdaDueDate($row->bonifico_data ?? null);
if (! empty($row->rda_importo_calcolata) && $row->rda_scadenza) {
if (! empty($row->rda_row_data)) {
$row->rda_stato = (\Carbon\Carbon::parse($row->rda_row_data)->lte(\Carbon\Carbon::parse($row->rda_scadenza)))
? 'versata in termine'
: 'versata in ritardo';
} else {
$row->rda_stato = 'da versare';
}
}
$row->protocollo_logico = 'debito';
if (! empty($row->rda_importo_calcolata)) {
$row->protocollo_logico = 'bonifico+rda';
}
}
if (! empty($row->rda_code) && empty($row->fe_display) && ! empty($row->ra_importo)) {
$row->fe_display = 'RA € ' . number_format((float) $row->ra_importo, 2, ',', '.');
}
if (! empty($row->rda_code) && $row->fe_fattura_id && ! empty($row->dt_spe) && ! empty($row->importo_spese) && empty($row->ra_data)) {
try {
$dt = \Carbon\Carbon::parse($row->dt_spe)->format('Y-m-d');
DB::table('contabilita_fatture_fornitori')
->where('id', $row->fe_fattura_id)
->update([
'data_versamento_ra' => $dt,
'ritenuta_importo' => $row->ra_importo,
'updated_at' => now(),
]);
$row->ra_data = $dt;
} catch (\Throwable) {
}
}
$row->tipo_movimento = null;
if (! empty($row->importo_spese)) {
$row->tipo_movimento = 'Spesa';
} elseif (! empty($row->importo_entrate)) {
$row->tipo_movimento = 'Entrata';
} elseif (! empty($row->importo_debiti)) {
$row->tipo_movimento = 'Debito';
} elseif (! empty($row->importo_crediti)) {
$row->tipo_movimento = 'Credito';
}
return $row;
});
$paginator->setCollection(
$mapped->filter(fn($row) => empty($row->is_rda_line))->values()
);
$indivRows = $items->filter(fn($r) => in_array(strtoupper((string) ($r->cod_spe ?? '')), ['I05', 'I06'], true) && ! empty($r->n_spe));
if ($indivRows->isNotEmpty()) {
$nSpeList = $indivRows->pluck('n_spe')->filter()->unique()->values()->all();
$dettMap = DB::connection('gescon_import')
->table('dett_pers')
->whereIn('n_spe', $nSpeList)
->select('n_spe')
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale')
->groupBy('n_spe')
->pluck('totale', 'n_spe')
->all();
$paginator->setCollection(
$paginator->getCollection()->map(function ($row) use ($dettMap) {
if (in_array(strtoupper((string) ($row->cod_spe ?? '')), ['I05', 'I06'], true) && ! empty($row->n_spe)) {
$detTot = $this->normalizeMoney($dettMap[$row->n_spe] ?? 0);
$imp = $this->normalizeMoney($row->importo_euro ?? $row->importo ?? 0);
$row->dett_pers_totale = $detTot;
$row->dett_pers_delta = $imp - $detTot;
$row->dett_pers_ok = abs($row->dett_pers_delta) <= 0.01;
}
return $row;
})
);
}
return $paginator;
}
public function getConsuntivoProperty(): array
{
$base = $this->buildOperazioniQuery();
$excludeCods = ['Z01', 'Z02', 'I05'];
if (DB::connection('gescon_import')->getSchemaBuilder()->hasTable('cre_deb_preced')) {
$creDebCodes = DB::connection('gescon_import')
->table('cre_deb_preced')
->pluck('cod_voc')
->filter()
->unique()
->values()
->all();
if (! empty($creDebCodes)) {
$excludeCods = array_values(array_unique(array_merge($excludeCods, $creDebCodes)));
}
}
$rows = $base
->where(function ($query): void {
$query->whereNull('compet')
->orWhere('compet', '!=', 'P');
})
->whereNotIn('cod_spe', $excludeCods)
->select('tabella', 'cod_spe')
->selectRaw("SUM(
CASE
WHEN (COALESCE(importo_spese, 0) + COALESCE(importo_entrate, 0) + COALESCE(importo_debiti, 0) + COALESCE(importo_crediti, 0)) = 0 THEN
CASE
WHEN cod_spe = 'RB1' THEN -1 * COALESCE(importo_euro, importo, 0)
WHEN natura2 IN ('E', 'ENTRATA', 'ENTRATE', 'CREDITO') THEN -1 * COALESCE(importo_euro, importo, 0)
ELSE COALESCE(importo_euro, importo, 0)
END
ELSE (COALESCE(importo_spese, 0) + COALESCE(importo_debiti, 0) - COALESCE(importo_entrate, 0) - COALESCE(importo_crediti, 0))
END
) as totale")
->groupBy('tabella', 'cod_spe')
->get();
$frazRows = collect();
if (Schema::connection('gescon_import')->hasTable('fraz_dett') && Schema::connection('gescon_import')->hasTable('fraz_gen')) {
$parteCol = null;
if (Schema::connection('gescon_import')->hasColumn('fraz_dett', 'parte')) {
$parteCol = 'parte';
} elseif (Schema::connection('gescon_import')->hasColumn('fraz_dett', 'spe_parte')) {
$parteCol = 'spe_parte';
}
$frazQuery = DB::connection('gescon_import')
->table('fraz_dett as fd')
->join('fraz_gen as fg', 'fg.Progressivo', '=', 'fd.rif_progressivo')
->select([
'fg.Progressivo as progressivo',
'fg.cod_voce as cod_voce_gen',
'fg.Importo_voce as importo_voce',
'fd.cod_voce as cod_voce_det',
'fd.Importo_parte as importo_parte',
]);
if ($parteCol) {
$frazQuery->addSelect("fd.$parteCol as parte");
}
if ($this->filterAnno) {
$frazQuery->where('fg.legacy_year', (string) $this->filterAnno);
}
$frazItems = $frazQuery->get();
if ($frazItems->isNotEmpty()) {
$sumParteByProg = [];
if ($parteCol) {
foreach ($frazItems as $r) {
$prog = (string) ($r->progressivo ?? '');
if ($prog === '') {
continue;
}
$parteVal = is_numeric($r->parte ?? null) ? (float) $r->parte : 0.0;
$sumParteByProg[$prog] = ($sumParteByProg[$prog] ?? 0.0) + $parteVal;
}
}
$totByVoce = [];
foreach ($frazItems as $r) {
$codVoceDet = trim((string) ($r->cod_voce_det ?? ''));
$codVoceGen = trim((string) ($r->cod_voce_gen ?? ''));
$codVoce = $codVoceDet !== '' ? $codVoceDet : $codVoceGen;
if ($codVoce === '') {
continue;
}
$impParte = is_numeric($r->importo_parte ?? null) ? (float) $r->importo_parte : 0.0;
$impVoce = is_numeric($r->importo_voce ?? null) ? (float) $r->importo_voce : 0.0;
if (abs($impParte) < 0.00001 && $parteCol) {
$prog = (string) ($r->progressivo ?? '');
$base = $sumParteByProg[$prog] ?? 0.0;
$parteVal = is_numeric($r->parte ?? null) ? (float) $r->parte : 0.0;
if ($base > 0 && $impVoce != 0.0) {
$impParte = $impVoce * ($parteVal / $base);
}
}
if (abs($impParte) < 0.00001) {
continue;
}
$totByVoce[$codVoce] = ($totByVoce[$codVoce] ?? 0.0) + $impParte;
}
$frazRows = collect($totByVoce)->map(function ($totale, $codVoce) {
return (object) [
'tabella' => null,
'cod_spe' => $codVoce,
'totale' => $totale,
'origin' => 'fraz',
];
})->values();
}
}
$i05Operazioni = collect();
if (Schema::connection('gescon_import')->hasTable('operazioni')) {
$i05Operazioni = DB::connection('gescon_import')
->table('operazioni')
->where('cod_spe', 'I05')
->when(
$this->resolveLegacyCodiceStabile() && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile'),
fn($q) => $q->where('cod_stabile', $this->resolveLegacyCodiceStabile())
)
->when($this->filterAnno, fn($q) => $q->whereYear('dt_spe', $this->filterAnno))
->get(['n_spe', 'importo_euro', 'importo']);
}
$nSpeI05 = $i05Operazioni
->pluck('n_spe')
->filter(fn($value) => is_numeric($value))
->map(fn($value) => (int) $value)
->unique()
->values()
->all();
$dettByNSpe = [];
if (! empty($nSpeI05) && Schema::connection('gescon_import')->hasTable('dett_pers')) {
$dettByNSpe = DB::connection('gescon_import')
->table('dett_pers')
->whereIn('n_spe', $nSpeI05)
->select('n_spe')
->selectRaw('SUM(COALESCE(importo_euro, importo, 0)) as totale')
->groupBy('n_spe')
->pluck('totale', 'n_spe')
->all();
}
$i05 = $i05Operazioni->sum(function ($row) use ($dettByNSpe) {
$nSpe = is_numeric($row->n_spe ?? null) ? (int) $row->n_spe : null;
if ($nSpe !== null && array_key_exists($nSpe, $dettByNSpe)) {
return $this->normalizeMoney($dettByNSpe[$nSpe]);
}
return $this->normalizeMoney($row->importo_euro ?? $row->importo ?? 0);
});
$allRows = $rows
->map(function ($r) {
$r->origin = 'base';
return $r;
})
->concat($frazRows)
->when($i05 !== null, function ($c) use ($i05) {
return $c->push((object) [
'tabella' => 'INDIV',
'cod_spe' => 'I05',
'totale' => $i05,
'origin' => 'i05',
]);
});
$codes = $allRows->pluck('cod_spe')->filter()->unique()->values()->all();
$vociMap = [];
$tabellaByCod = [];
if (! empty($codes)) {
$vocRows = DB::connection('gescon_import')
->table('voc_spe')
->whereIn('cod', $codes)
->get(['cod', 'descriz', 'tabella']);
$vociMap = $vocRows->mapWithKeys(fn($v) => [$v->cod => $v->descriz])->all();
$tabellaByCod = $vocRows->mapWithKeys(fn($v) => [$v->cod => $v->tabella])->all();
}
$tabCodes = $allRows->map(function ($r) use ($tabellaByCod) {
return $r->tabella ?: ($tabellaByCod[$r->cod_spe ?? ''] ?? null);
})->filter()->unique()->values()->all();
$tabellaMap = [];
$tabellaInfo = [];
if (! empty($tabCodes)) {
$tabellaMap = DB::connection('gescon_import')
->table('tabelle_millesimali')
->whereIn('cod_tabella', $tabCodes)
->pluck('denominazione', 'cod_tabella')
->all();
$stagingConn = DB::connection('gescon_import');
$hasTipologia = $stagingConn->getSchemaBuilder()->hasColumn('tabelle_millesimali', 'tipologia');
$hasNord = $stagingConn->getSchemaBuilder()->hasColumn('tabelle_millesimali', 'nord');
$hasOrdinamento = $stagingConn->getSchemaBuilder()->hasColumn('tabelle_millesimali', 'ordinamento');
$selectFields = ['cod_tabella', 'denominazione', 'descrizione'];
if ($hasTipologia) {
$selectFields[] = 'tipologia';
}
if ($hasNord) {
$selectFields[] = 'nord';
} elseif ($hasOrdinamento) {
$selectFields[] = 'ordinamento as nord';
}
$tabellaInfo = $stagingConn
->table('tabelle_millesimali')
->whereIn('cod_tabella', $tabCodes)
->get($selectFields)
->map(function ($t) use ($hasTipologia, $hasNord) {
if (!$hasTipologia) {
$t->tipologia = null;
}
if (!$hasNord) {
$t->nord = $t->nord ?? null;
}
return $t;
})
->keyBy('cod_tabella')
->all();
}
$rows = $allRows->map(function ($r) use ($vociMap, $tabellaByCod, $tabellaMap, $tabellaInfo) {
$tabella = $r->tabella ?: ($tabellaByCod[$r->cod_spe ?? ''] ?? null);
$forcedHeader = null;
$codSpe = strtoupper((string) ($r->cod_spe ?? ''));
if (in_array($codSpe, ['I05', 'I06'], true)) {
$tabella = 'INDIV.';
$forcedHeader = 'INDIV. ADDEBITI INDIVIDUALI';
}
$info = $tabella && isset($tabellaInfo[$tabella]) ? $tabellaInfo[$tabella] : null;
$denom = $info?->denominazione ?? ($tabellaMap[$tabella ?? ''] ?? null);
$descr = $info?->descrizione ?? null;
$header = trim(($tabella ?? '—') . ' ' . ($denom ?? $descr ?? ''));
if ($denom && $descr && $denom !== $descr) {
$header = trim(($tabella ?? '—') . ' ' . $denom . ' (' . $descr . ')');
}
if ($forcedHeader) {
$header = $forcedHeader;
}
return [
'tabella' => $tabella ?? '—',
'tabella_descrizione' => $tabellaMap[$tabella ?? ''] ?? null,
'tabella_header' => $header,
'tabella_tipologia' => $info?->tipologia ?? null,
'tabella_nord' => is_numeric($info?->nord ?? null) ? (int) $info->nord : 9999,
'cod_spe' => $r->cod_spe ?? '—',
'voce' => $vociMap[$r->cod_spe ?? ''] ?? null,
'totale' => (float) ($r->totale ?? 0),
];
})
->filter(function (array $r): bool {
$tot = (float) ($r['totale'] ?? 0);
if ($tot !== 0.0) {
return true;
}
$tab = trim((string) ($r['tabella'] ?? ''));
$voce = trim((string) ($r['voce'] ?? ''));
if ($tab === '—') {
$tab = '';
}
if ($voce === '—') {
$voce = '';
}
return $tab !== '' || $voce !== '';
})
->values();
$rows = $rows->sortBy(function ($r) {
return sprintf('%05d|%s|%s', (int) ($r['tabella_nord'] ?? 9999), $r['tabella'] ?? '', $r['cod_spe'] ?? '');
})->values();
$groups = [
'ordinarie' => [],
'riscaldamento' => [],
'straordinarie' => [],
'acqua' => [],
];
foreach ($rows as $r) {
$tab = (string) ($r['tabella'] ?? '');
$tip = strtoupper((string) ($r['tabella_tipologia'] ?? ''));
$groupKey = 'ordinarie';
if ($tab === 'ACQUA' || str_starts_with($tab, 'AC')) {
$groupKey = 'acqua';
} elseif ($tip === 'R' || $tip === 'RISCALDAMENTO') {
$groupKey = 'riscaldamento';
} elseif (in_array($tip, ['S', 'STRAORDINARIA'], true)) {
$groupKey = 'straordinarie';
}
if (! isset($groups[$groupKey][$tab])) {
$groups[$groupKey][$tab] = [
'header' => $r['tabella_header'] ?? $tab,
'nord' => $r['tabella_nord'] ?? 9999,
'rows' => [],
'subtotal' => 0.0,
];
}
$groups[$groupKey][$tab]['rows'][] = $r;
$groups[$groupKey][$tab]['subtotal'] += (float) ($r['totale'] ?? 0);
}
foreach ($groups as $k => $tabs) {
uasort($tabs, function ($a, $b) {
return ($a['nord'] ?? 9999) <=> ($b['nord'] ?? 9999);
});
$groups[$k] = $tabs;
}
return $groups;
}
public function getIncassiProperty()
{
if (! Schema::connection('gescon_import')->hasTable('incassi')) {
return null;
}
$q = DB::connection('gescon_import')->table('incassi');
$hasCondomin = Schema::connection('gescon_import')->hasTable('condomin');
if ($hasCondomin) {
$hasIncassiCodStabile = Schema::connection('gescon_import')->hasColumn('incassi', 'cod_stabile');
$hasCondominCodStabile = Schema::connection('gescon_import')->hasColumn('condomin', 'cod_stabile');
$hasCondominLegacyYear = Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year');
$q->leftJoin('condomin', function ($join) use ($hasIncassiCodStabile, $hasCondominCodStabile, $hasCondominLegacyYear): void {
$join->on('condomin.cod_cond', '=', 'incassi.cod_cond');
if ($hasIncassiCodStabile && $hasCondominCodStabile) {
$join->on('condomin.cod_stabile', '=', 'incassi.cod_stabile');
}
if ($hasCondominLegacyYear) {
$join->where('condomin.legacy_year', '=', '0001');
}
});
}
$this->applyIncassiFilters($q);
$codStabile = $this->resolveLegacyCodiceStabile();
$orderCol = Schema::connection('gescon_import')->hasColumn('incassi', 'dt_empag') ? 'dt_empag' : 'id';
$condominCols = Schema::connection('gescon_import')->getColumnListing('condomin');
$selects = ['incassi.*'];
$mapping = [
'scala' => 'cond_scala',
'interno' => 'cond_interno',
'cognome' => 'cond_cognome',
'nome' => 'cond_nome',
'proprietario_denominazione' => 'cond_proprietario_denominazione',
'inquilino_denominazione' => 'cond_inquilino_denominazione',
'inquil_nome' => 'cond_inquil_nome',
];
foreach ($mapping as $col => $alias) {
if (in_array($col, $condominCols, true)) {
$selects[] = "condomin.{$col} as {$alias}";
}
}
$paginator = $q
->select($selects)
->orderByDesc($orderCol)
->orderByDesc('id')
->paginate($this->perPage);
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
$personaByCond = [];
$rateRowsByCond = [];
$codConds = $paginator->getCollection()
->pluck('cod_cond')
->filter(fn($v) => is_numeric($v))
->map(fn($v) => (int) $v)
->unique()
->values()
->all();
if (! empty($codConds) && Schema::connection('gescon_import')->hasTable('rate')) {
$rateQuery = DB::connection('gescon_import')
->table('rate')
->whereIn('cod_cond', $codConds);
if ($codStabile && Schema::connection('gescon_import')->hasColumn('rate', 'cod_stabile')) {
$rateQuery->where('cod_stabile', $codStabile);
}
if ($this->filterAnno) {
$year = (int) $this->filterAnno;
$rateQuery->where(function ($q) use ($year): void {
if (Schema::connection('gescon_import')->hasColumn('rate', 'data_emissione')) {
$q->orWhereYear('data_emissione', $year);
}
if (Schema::connection('gescon_import')->hasColumn('rate', 'data_scadenza')) {
$q->orWhereYear('data_scadenza', $year);
}
if (Schema::connection('gescon_import')->hasColumn('rate', 'data_pagamento')) {
$q->orWhereYear('data_pagamento', $year);
}
});
}
$rateRows = $rateQuery->get([
'cod_cond',
'n_emissione',
'data_emissione',
'data_scadenza',
'importo',
'importo_euro',
'causale',
'tipo_rata',
'pagata',
'data_pagamento',
]);
$rateRowsByCond = $rateRows
->groupBy(fn($r) => (int) ($r->cod_cond ?? 0))
->map(fn($rows) => $rows->values()->all())
->all();
}
if ($activeStabileId && Schema::hasTable('unita_immobiliari') && Schema::hasTable('persone_unita_relazioni') && Schema::hasTable('persone')) {
if (! empty($codConds) && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
$unitaRows = DB::table('unita_immobiliari')
->where('stabile_id', (int) $activeStabileId)
->whereIn('legacy_cond_id', $codConds)
->get(['id', 'legacy_cond_id']);
$unitaIds = $unitaRows->pluck('id')->map(fn($v) => (int) $v)->all();
$legacyByUnita = $unitaRows->mapWithKeys(fn($r) => [(int) $r->id => (int) $r->legacy_cond_id])->all();
if (! empty($unitaIds)) {
$relRows = DB::table('persone_unita_relazioni as pur')
->join('persone as p', 'p.id', '=', 'pur.persona_id')
->whereIn('pur.unita_id', $unitaIds)
->where('pur.attivo', true)
->where(function ($q): void {
$q->whereNull('pur.data_fine')->orWhereDate('pur.data_fine', '>=', now()->toDateString());
})
->select(['pur.persona_id', 'pur.unita_id', 'pur.tipo_relazione', 'pur.data_inizio'])
->orderByDesc('pur.data_inizio')
->orderByDesc('pur.id')
->get();
foreach ($relRows as $rel) {
$unitaId = (int) ($rel->unita_id ?? 0);
$codCond = (int) ($legacyByUnita[$unitaId] ?? 0);
if ($codCond <= 0) {
continue;
}
$tipo = strtolower(trim((string) ($rel->tipo_relazione ?? '')));
$pid = (int) ($rel->persona_id ?? 0);
if ($pid <= 0) {
continue;
}
$roleClass = 'C';
if (in_array($tipo, ['inquilino', 'conduttore', 'affittuario', 'locatario'], true)) {
$roleClass = 'I';
}
$score = $this->relationScoreForIncasso($tipo, $roleClass);
$current = $personaByCond[$codCond][$roleClass] ?? null;
if (! $current || $score > ($current['score'] ?? -1)) {
$personaByCond[$codCond][$roleClass] = [
'persona_id' => $pid,
'score' => $score,
];
}
}
}
}
}
$paginator->setCollection(
$paginator->getCollection()->map(function ($row) use ($personaByCond, $rateRowsByCond) {
$tipo = strtoupper(trim((string) ($row->cond_inq ?? $row->cond_inquil ?? '')));
$nominativo = '';
if ($tipo === 'I') {
$nominativo = trim((string) ($row->cond_inquilino_denominazione ?? ''));
if ($nominativo === '') {
$nominativo = trim((string) ($row->cond_inquil_nome ?? ''));
}
} else {
$nominativo = trim((string) ($row->cond_proprietario_denominazione ?? ''));
if ($nominativo === '') {
$nominativo = trim((string) (($row->cond_cognome ?? '') . ' ' . ($row->cond_nome ?? '')));
}
}
$row->nominativo_pagante = $nominativo !== '' ? $nominativo : '—';
$row->scala_interno_short = $this->formatScalaInternoShort($row->cond_scala ?? null, $row->cond_interno ?? null);
$row->data_visuale = $row->dt_empag ?? $row->data_incasso ?? null;
$row->riferimento_visuale = $row->riferimento_pagamento ?? $row->n_riferimento ?? $row->n_rata_riferimento ?? $row->n_ricevuta ?? $row->n_mese ?? null;
$row->importo_visualizzato = $this->resolveIncassoImporto($row);
$codCond = is_numeric($row->cod_cond ?? null) ? (int) $row->cod_cond : 0;
$personaRef = null;
if ($codCond > 0) {
$personaRef = $personaByCond[$codCond][$tipo] ?? ($personaByCond[$codCond]['C'] ?? null);
}
$row->persona_id = $personaRef['persona_id'] ?? null;
$row->persona_url = $row->persona_id
? \App\Filament\Pages\Gescon\PersonaScheda::getUrl(['record' => (int) $row->persona_id], panel : 'admin-filament')
: null;
$row->rata_match = null;
$row->rata_match_source = null;
if ($codCond > 0 && ! empty($rateRowsByCond[$codCond] ?? [])) {
$match = $this->findRateMatchForIncasso($row, $rateRowsByCond[$codCond]);
if ($match) {
$row->rata_match = $match;
$row->rata_match_source = 'rate';
}
}
return $row;
})
);
return $paginator;
}
public function getIncassiRifSummaryProperty(): array
{
if (! Schema::connection('gescon_import')->hasTable('incassi')) {
return ['rows' => [], 'totale' => 0.0, 'totale_ccb' => 0.0, 'count' => 0];
}
$query = DB::connection('gescon_import')->table('incassi');
$this->applyIncassiFilters($query);
$select = ['id'];
foreach (['riferimento_pagamento', 'n_riferimento', 'n_rata_riferimento', 'n_ricevuta', 'n_mese', 'importo_euro', 'importo', 'importo_pagato_euro', 'dt_empag', 'data_incasso', 'cod_cassa'] as $col) {
if (Schema::connection('gescon_import')->hasColumn('incassi', $col)) {
$select[] = $col;
}
}
$rows = $query->get($select);
$summary = [];
$totale = 0.0;
$totaleCcb = 0.0;
foreach ($rows as $row) {
$rif = trim((string) ($row->riferimento_pagamento ?? $row->n_riferimento ?? $row->n_rata_riferimento ?? $row->n_ricevuta ?? $row->n_mese ?? ''));
if ($rif === '') {
$rif = '—';
}
$importo = $this->resolveIncassoImporto($row);
$totale += $importo;
$isCcb = strtoupper(trim((string) ($row->cod_cassa ?? ''))) === 'CCB';
if ($isCcb) {
$totaleCcb += $importo;
}
if (! isset($summary[$rif])) {
$summary[$rif] = [
'rif' => $rif,
'count' => 0,
'totale' => 0.0,
'totale_ccb' => 0.0,
'ultima_data' => null,
'casse' => [],
];
}
$summary[$rif]['count']++;
$summary[$rif]['totale'] += $importo;
if ($isCcb) {
$summary[$rif]['totale_ccb'] += $importo;
}
$cassa = trim((string) ($row->cod_cassa ?? ''));
if ($cassa !== '') {
$summary[$rif]['casse'][$cassa] = true;
}
$data = $row->dt_empag ?? $row->data_incasso ?? null;
if (! empty($data)) {
try {
$ymd = (string) \Carbon\Carbon::parse($data)->format('Y-m-d');
if (empty($summary[$rif]['ultima_data']) || $ymd > (string) $summary[$rif]['ultima_data']) {
$summary[$rif]['ultima_data'] = $ymd;
}
} catch (\Throwable) {
}
}
}
$rowsOut = collect($summary)
->map(function (array $row) {
$row['totale'] = round((float) ($row['totale'] ?? 0), 2);
$row['totale_ccb'] = round((float) ($row['totale_ccb'] ?? 0), 2);
$row['casse'] = implode(', ', array_keys($row['casse'] ?? []));
return $row;
})
->sortByDesc('totale')
->values()
->all();
return [
'rows' => $rowsOut,
'totale' => round($totale, 2),
'totale_ccb' => round($totaleCcb, 2),
'count' => (int) $rows->count(),
];
}
public function getRaMonthlyTributoSummaryProperty(): array
{
if (! Schema::hasTable('contabilita_fatture_fornitori')) {
return ['rows' => [], 'totale_fatta' => 0.0, 'totale_versata' => 0.0, 'totale_delta' => 0.0];
}
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
$query = DB::table('contabilita_fatture_fornitori')
->when($activeStabileId, fn($q) => $q->where('stabile_id', (int) $activeStabileId))
->whereRaw('COALESCE(ritenuta_importo, 0) <> 0');
if ($this->filterAnno) {
$year = (int) $this->filterAnno;
$query->where(function ($q) use ($year): void {
$q->whereYear('data_documento', $year)
->orWhereYear('data_versamento_ra', $year);
});
}
$rows = $query->get([
'fornitore_id',
'data_documento',
'data_versamento_ra',
'ritenuta_importo',
'ritenuta_codice_tributo',
]);
$fornitoreIds = $rows
->pluck('fornitore_id')
->filter(fn($v) => is_numeric($v))
->map(fn($v) => (int) $v)
->unique()
->values()
->all();
$tributoByFornitore = $this->resolveTributoByFornitoreIdMap($fornitoreIds);
$summary = [];
$totFatta = 0.0;
$totVersata = 0.0;
foreach ($rows as $row) {
$importo = abs($this->normalizeMoney($row->ritenuta_importo ?? 0));
if ($importo <= 0) {
continue;
}
$tributo = strtoupper(trim((string) ($row->ritenuta_codice_tributo ?? '')));
$fornitoreId = is_numeric($row->fornitore_id ?? null) ? (int) $row->fornitore_id : null;
if ($tributo === '' && $fornitoreId && ! empty($tributoByFornitore[$fornitoreId])) {
$tributo = (string) $tributoByFornitore[$fornitoreId];
}
if ($tributo === '') {
$tributo = '1040';
}
if (! empty($row->data_documento)) {
try {
$mese = (string) \Carbon\Carbon::parse($row->data_documento)->format('Y-m');
$key = $mese . '|' . $tributo;
if (! isset($summary[$key])) {
$summary[$key] = ['mese' => $mese, 'tributo' => $tributo, 'fatta' => 0.0, 'versata' => 0.0];
}
$summary[$key]['fatta'] += $importo;
$totFatta += $importo;
} catch (\Throwable) {
}
}
if (! empty($row->data_versamento_ra)) {
try {
$mese = (string) \Carbon\Carbon::parse($row->data_versamento_ra)->format('Y-m');
$key = $mese . '|' . $tributo;
if (! isset($summary[$key])) {
$summary[$key] = ['mese' => $mese, 'tributo' => $tributo, 'fatta' => 0.0, 'versata' => 0.0];
}
$summary[$key]['versata'] += $importo;
$totVersata += $importo;
} catch (\Throwable) {
}
}
}
$rowsOut = collect($summary)
->map(function (array $row) {
$fatta = round((float) ($row['fatta'] ?? 0), 2);
$versata = round((float) ($row['versata'] ?? 0), 2);
$row['fatta'] = $fatta;
$row['versata'] = $versata;
$row['delta'] = round($fatta - $versata, 2);
return $row;
})
->sortBy(['mese', 'tributo'])
->values()
->all();
return [
'rows' => $rowsOut,
'totale_fatta' => round($totFatta, 2),
'totale_versata' => round($totVersata, 2),
'totale_delta' => round($totFatta - $totVersata, 2),
];
}
public function getFattureRicevuteConsolidateProperty()
{
if (! Schema::hasTable('contabilita_fatture_fornitori')) {
return null;
}
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
if (! $activeStabileId) {
return null;
}
$query = DB::table('contabilita_fatture_fornitori as ff')
->leftJoin('fornitori as f', 'f.id', '=', 'ff.fornitore_id')
->leftJoin('fatture_elettroniche as fe', 'fe.id', '=', 'ff.fattura_elettronica_id')
->where('ff.stabile_id', (int) $activeStabileId);
if ($this->filterAnno) {
$query->whereYear('ff.data_documento', (int) $this->filterAnno);
}
if (! empty($this->filterCodForn)) {
$needle = trim((string) $this->filterCodForn);
$query->where(function ($q) use ($needle): void {
$q->where('f.old_id', $needle)
->orWhere('f.old_id', ltrim($needle, '0'));
});
}
if (! empty($this->search)) {
$needle = '%' . trim((string) $this->search) . '%';
$query->where(function ($q) use ($needle): void {
$q->orWhere('ff.numero_documento', 'like', $needle)
->orWhere('ff.descrizione', 'like', $needle)
->orWhere('f.ragione_sociale', 'like', $needle)
->orWhere('fe.sdi_file', 'like', $needle);
});
}
return $query
->select([
'ff.id',
'ff.data_documento',
'ff.numero_documento',
'ff.totale',
'ff.netto_da_pagare',
'ff.stato',
'ff.data_pagamento',
'ff.fattura_elettronica_id',
'ff.ritenuta_importo',
'ff.data_versamento_ra',
'f.old_id as cod_fornitore_legacy',
'f.ragione_sociale as fornitore_nome',
'fe.sdi_file as fe_sdi_file',
])
->orderByDesc('ff.data_documento')
->orderByDesc('ff.id')
->paginate($this->perPage, ['*'], 'fattureRicevutePage');
}
public function getFattureLegacyProperty()
{
$sourceTable = null;
if (Schema::hasTable('stg_fatture_gescon')) {
$sourceTable = 'stg_fatture_gescon';
} elseif (Schema::connection('gescon_import')->hasTable('fatture')) {
$sourceTable = 'fatture';
}
if (! $sourceTable) {
return null;
}
if ($sourceTable === 'stg_fatture_gescon') {
$query = DB::table('stg_fatture_gescon as f');
} else {
$query = DB::connection('gescon_import')->table('fatture as f');
}
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
$activeTenantId = null;
$legacyStabileIdCandidates = [];
$legacyCodiceCandidates = [];
$legacyCode = $this->resolveLegacyCodiceStabile();
if ($legacyCode) {
if (Schema::hasTable('stabili')) {
$stabileRow = DB::table('stabili')
->where('codice_stabile', $legacyCode)
->first(['id', 'old_id', 'cod_stabile', 'codice_stabile', 'amministratore_id']);
if ($stabileRow && ! empty($stabileRow->amministratore_id)) {
$activeTenantId = (string) $stabileRow->amministratore_id;
}
foreach ([(string) ($stabileRow->old_id ?? ''), (string) ($stabileRow->cod_stabile ?? ''), (string) ($stabileRow->codice_stabile ?? $legacyCode)] as $value) {
$trim = trim($value);
if ($trim === '') {
continue;
}
$legacyCodiceCandidates[] = $trim;
$noZero = ltrim($trim, '0');
if ($noZero !== '') {
$legacyCodiceCandidates[] = $noZero;
}
if (is_numeric($trim)) {
$legacyStabileIdCandidates[] = (int) $trim;
$normalized = ltrim($trim, '0');
if ($normalized !== '' && is_numeric($normalized)) {
$legacyStabileIdCandidates[] = (int) $normalized;
}
}
}
} else {
$legacyCodiceCandidates[] = $legacyCode;
$noZero = ltrim($legacyCode, '0');
if ($noZero !== '') {
$legacyCodiceCandidates[] = $noZero;
}
if (is_numeric($legacyCode)) {
$legacyStabileIdCandidates[] = (int) $legacyCode;
}
}
$legacyStabileIdCandidates = array_values(array_unique(array_filter($legacyStabileIdCandidates, fn($v) => is_int($v) && $v > 0)));
$legacyCodiceCandidates = array_values(array_unique(array_filter($legacyCodiceCandidates, fn($v) => is_string($v) && $v !== '')));
if ($sourceTable === 'stg_fatture_gescon' && Schema::hasColumn('stg_fatture_gescon', 'legacy_id_stabile') && ! empty($legacyStabileIdCandidates)) {
$query->whereIn('f.legacy_id_stabile', $legacyStabileIdCandidates);
} elseif ($sourceTable === 'fatture' && Schema::connection('gescon_import')->hasColumn('fatture', 'cod_stabile') && ! empty($legacyCodiceCandidates)) {
$query->whereIn('f.cod_stabile', $legacyCodiceCandidates);
}
}
if ($sourceTable === 'stg_fatture_gescon' && $activeTenantId !== null && Schema::hasColumn('stg_fatture_gescon', 'tenant_id')) {
$query->where('f.tenant_id', $activeTenantId);
}
if ($this->filterAnno) {
$year = (int) $this->filterAnno;
if ($sourceTable === 'stg_fatture_gescon' && Schema::hasColumn('stg_fatture_gescon', 'data_fattura')) {
$query->whereYear('f.data_fattura', $year);
} elseif ($sourceTable === 'fatture' && Schema::connection('gescon_import')->hasColumn('fatture', 'data_fattura')) {
$query->whereYear('f.data_fattura', $year);
}
}
if (! empty($this->filterCodForn)) {
if ($sourceTable === 'stg_fatture_gescon' && Schema::hasColumn('stg_fatture_gescon', 'cod_fornitore')) {
$query->where('f.cod_fornitore', trim((string) $this->filterCodForn));
}
if ($sourceTable === 'fatture' && Schema::connection('gescon_import')->hasColumn('fatture', 'cod_fornitore')) {
$query->where('f.cod_fornitore', trim((string) $this->filterCodForn));
}
}
if (! empty($this->search)) {
$needle = '%' . trim((string) $this->search) . '%';
$query->where(function ($q) use ($needle, $sourceTable): void {
$fields = ['numero_fattura', 'descrizione', 'descrizione_sintetica', 'descrizione_corpo', 'note'];
if ($sourceTable === 'stg_fatture_gescon') {
$fields[] = 'cod_fornitore';
} else {
$fields[] = 'cod_fornitore';
}
foreach ($fields as $field) {
if ($sourceTable === 'stg_fatture_gescon' && ! Schema::hasColumn('stg_fatture_gescon', $field)) {
continue;
}
if ($sourceTable === 'fatture' && ! Schema::connection('gescon_import')->hasColumn('fatture', $field)) {
continue;
}
$q->orWhere('f.' . $field, 'like', $needle);
}
});
}
if ($sourceTable === 'stg_fatture_gescon') {
$select = [
'f.id as row_id',
'f.legacy_id_fatture',
'f.cod_fornitore',
'f.numero_fattura',
'f.data_fattura',
'f.data_pagamento',
'f.descrizione_sintetica',
'f.descrizione_corpo',
'f.note',
'f.imponibile',
'f.importo_iva',
'f.totale_fattura',
'f.importo_netto',
'f.importo_rda',
'f.riferimento',
'f.reg_gestione',
'f.reg_nstra',
];
} else {
$select = [
'f.id as row_id',
'f.id as legacy_id_fatture',
'f.cod_fornitore',
'f.numero_fattura',
'f.data_fattura',
'f.data_pagamento',
'f.descrizione',
DB::raw('NULL as descrizione_corpo'),
'f.note',
DB::raw('COALESCE(f.importo_totale, f.importo_euro, 0) as imponibile'),
'f.iva as importo_iva',
DB::raw('COALESCE(f.importo_totale, f.importo_euro, 0) as totale_fattura'),
DB::raw('COALESCE(f.importo_totale, f.importo_euro, 0) as importo_netto'),
'f.ritenuta as importo_rda',
DB::raw('NULL as riferimento'),
DB::raw('NULL as reg_gestione'),
DB::raw('NULL as reg_nstra'),
];
}
$paginator = $query
->select($select)
->orderByDesc('f.data_fattura')
->orderByDesc('f.id')
->paginate($this->perPage);
$codFornitori = $paginator->getCollection()
->pluck('cod_fornitore')
->filter(fn($v) => $v !== null && trim((string) $v) !== '')
->map(fn($v) => trim((string) $v))
->unique()
->values()
->all();
$fornitoriMap = [];
$fornitoriIdMap = [];
$fornitoriTributoMap = [];
if (! empty($codFornitori)) {
if (Schema::hasTable('stg_fornitori_gescon')) {
$fornRows = DB::table('stg_fornitori_gescon')
->whereIn('cod_forn', $codFornitori)
->get(['cod_forn', 'denominazione', 'cognome', 'nome', 'raw']);
} else {
$stagingConn = DB::connection('gescon_import');
$tableName = $stagingConn->getSchemaBuilder()->hasTable('mdb_fornitori') ? 'mdb_fornitori' : 'fornitori';
$hasDenom = $stagingConn->getSchemaBuilder()->hasColumn($tableName, 'denominazione');
$hasRagione = $stagingConn->getSchemaBuilder()->hasColumn($tableName, 'ragione_sociale');
$selectFields = ['cod_forn'];
if ($hasDenom) {
$selectFields[] = 'denominazione';
} elseif ($hasRagione) {
$selectFields[] = 'ragione_sociale as denominazione';
}
if ($stagingConn->getSchemaBuilder()->hasColumn($tableName, 'cognome')) {
$selectFields[] = 'cognome';
}
if ($stagingConn->getSchemaBuilder()->hasColumn($tableName, 'nome')) {
$selectFields[] = 'nome';
}
$fornRows = $stagingConn->table($tableName)
->whereIn('cod_forn', $codFornitori)
->get($selectFields);
}
foreach ($fornRows as $row) {
$code = trim((string) ($row->cod_forn ?? ''));
if ($code === '') {
continue;
}
$label = trim((string) ($row->denominazione ?? ''));
if ($label === '') {
$label = trim((string) (($row->nome ?? '') . ' ' . ($row->cognome ?? '')));
}
$fornitoriMap[$code] = $label !== '' ? $label : null;
$rawTributo = null;
if (isset($row->raw)) {
$raw = json_decode((string) $row->raw, true);
if (is_array($raw)) {
$rawTributo = strtoupper(trim((string) ($raw['trib_1019_1020'] ?? '')));
}
}
if ($rawTributo !== '') {
$fornitoriTributoMap[$code] = $rawTributo;
$codeNoZero = ltrim($code, '0');
if ($codeNoZero !== '') {
$fornitoriTributoMap[$codeNoZero] = $rawTributo;
}
}
}
if (Schema::hasTable('fornitori')) {
$localRows = DB::table('fornitori')
->whereIn('old_id', $codFornitori)
->get(['id', 'old_id', 'ragione_sociale', 'nome', 'cognome']);
foreach ($localRows as $local) {
$fornitoriIdMap[(string) $local->old_id] = (int) $local->id;
if (empty($fornitoriMap[(string) $local->old_id])) {
$label = $local->ragione_sociale ?: trim(($local->nome ?? '') . ' ' . ($local->cognome ?? ''));
if ($label !== '') {
$fornitoriMap[(string) $local->old_id] = $label;
}
}
}
}
}
$fattureLocali = [];
$fattureFeByDoc = [];
$fattureFeByFornitoreDoc = [];
if (! empty($fornitoriIdMap) && Schema::hasTable('contabilita_fatture_fornitori')) {
$fattureRows = DB::table('contabilita_fatture_fornitori')
->when($activeStabileId, fn($q) => $q->where('stabile_id', (int) $activeStabileId))
->whereIn('fornitore_id', array_values($fornitoriIdMap))
->get(['id', 'fornitore_id', 'fattura_elettronica_id', 'numero_documento', 'data_documento', 'totale', 'stato', 'data_versamento_ra', 'ritenuta_importo', 'ritenuta_codice_tributo']);
foreach ($fattureRows as $fattura) {
$numero = $this->normalizeInvoiceNumber($fattura->numero_documento ?? null);
$data = ! empty($fattura->data_documento)
? (string) \Carbon\Carbon::parse($fattura->data_documento)->format('Y-m-d')
: null;
if (! $numero || ! $data) {
continue;
}
$key = (int) $fattura->fornitore_id . '|' . $numero . '|' . $data;
$fattureLocali[$key][] = $fattura;
$globalKey = $numero . '|' . $data;
$fattureFeByDoc[$globalKey][] = $fattura;
$fattureFeByFornitoreDoc[$key][] = $fattura;
}
}
$feGlobalByDoc = [];
$feByFornitoreDoc = [];
if ($activeStabileId && Schema::hasTable('fatture_elettroniche')) {
$feRows = DB::table('fatture_elettroniche')
->where('stabile_id', (int) $activeStabileId)
->get(['id', 'fornitore_id', 'numero_fattura', 'data_fattura']);
foreach ($feRows as $fe) {
$numero = $this->normalizeInvoiceNumber($fe->numero_fattura ?? null);
$data = ! empty($fe->data_fattura)
? (string) \Carbon\Carbon::parse($fe->data_fattura)->format('Y-m-d')
: null;
if (! $numero || ! $data) {
continue;
}
$globalKey = $numero . '|' . $data;
$feGlobalByDoc[$globalKey][] = $fe;
$fornitoreId = is_numeric($fe->fornitore_id ?? null) ? (int) $fe->fornitore_id : 0;
if ($fornitoreId > 0) {
$key = $fornitoreId . '|' . $numero . '|' . $data;
$feByFornitoreDoc[$key][] = $fe;
}
}
}
$legacyRitenutaById = [];
if ($sourceTable === 'stg_fatture_gescon' && Schema::connection('gescon_import')->hasTable('fatture')) {
$legacyIds = $paginator->getCollection()
->pluck('legacy_id_fatture')
->filter(fn($v) => is_numeric($v))
->map(fn($v) => (int) $v)
->unique()
->values()
->all();
if (! empty($legacyIds)) {
$legacyRitenutaById = DB::connection('gescon_import')
->table('fatture')
->whereIn('id', $legacyIds)
->get(['id', 'ritenuta'])
->mapWithKeys(fn($r) => [(int) $r->id => $this->normalizeMoney($r->ritenuta ?? 0)])
->all();
}
}
$rdaCodeByInvoice = [];
$rdaRowsByCode = [];
if (Schema::connection('gescon_import')->hasTable('operazioni')) {
$opQuery = DB::connection('gescon_import')->table('operazioni');
if (! empty($legacyCodiceCandidates) && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
$opQuery->whereIn('cod_stabile', $legacyCodiceCandidates);
}
if ($this->filterAnno && Schema::connection('gescon_import')->hasColumn('operazioni', 'dt_spe')) {
$opQuery->whereYear('dt_spe', (int) $this->filterAnno);
}
$opSelect = ['benef'];
foreach (['rif_rda', 'dt_spe', 'cod_for', 'num_fat', 'netto_vers_rda', 'importo_euro_770', 'importo_euro', 'importo'] as $column) {
if (Schema::connection('gescon_import')->hasColumn('operazioni', $column)) {
$opSelect[] = $column;
}
}
$opRows = $opQuery->get(array_values(array_unique($opSelect)));
foreach ($opRows as $opRow) {
$benef = trim((string) ($opRow->benef ?? ''));
$benefUpper = strtoupper($benef);
$isRdaLine = $benefUpper !== '' && str_starts_with($benefUpper, 'RDA:');
$rdaCode = trim((string) ($opRow->rif_rda ?? ''));
if ($rdaCode === '' && $isRdaLine) {
$rdaCode = trim(substr($benef, 4));
}
if ($rdaCode === '') {
continue;
}
if ($isRdaLine) {
$rdaAmount = abs($this->normalizeMoney(
$opRow->netto_vers_rda ?? $opRow->importo_euro_770 ?? $opRow->importo_euro ?? $opRow->importo ?? 0
));
$existingDate = $rdaRowsByCode[$rdaCode]['data'] ?? null;
$currentDate = ! empty($opRow->dt_spe) ? (string)
\Carbon\Carbon::parse($opRow->dt_spe)->format('Y-m-d') : null;
if (! isset($rdaRowsByCode[$rdaCode])) {
$rdaRowsByCode[$rdaCode] = [
'importo' => $rdaAmount,
'data' => $currentDate,
];
} elseif ($currentDate && ($existingDate === null || $currentDate >= (string) $existingDate)) {
$rdaRowsByCode[$rdaCode]['data'] = $currentDate;
if ($rdaAmount > 0) {
$rdaRowsByCode[$rdaCode]['importo'] = $rdaAmount;
}
} elseif (($rdaRowsByCode[$rdaCode]['importo'] ?? 0) <= 0 && $rdaAmount > 0) {
$rdaRowsByCode[$rdaCode]['importo'] = $rdaAmount;
}
continue;
}
$numeroNorm = $this->normalizeInvoiceNumber($opRow->num_fat ?? null);
if ($numeroNorm === null) {
continue;
}
$codForRaw = trim((string) ($opRow->cod_for ?? ''));
if ($codForRaw === '') {
continue;
}
$fornCandidates = array_values(array_unique(array_filter([
$codForRaw,
ltrim($codForRaw, '0'),
], fn($v) => is_string($v) && $v !== '')));
foreach ($fornCandidates as $fornCode) {
$rdaCodeByInvoice[$fornCode . '|' . $numeroNorm] = $rdaCode;
}
}
}
$paginator->setCollection(
$paginator->getCollection()->map(function ($row) use ($fornitoriMap, $fornitoriIdMap, $fornitoriTributoMap, $fattureLocali, $fattureFeByDoc, $fattureFeByFornitoreDoc, $legacyRitenutaById, $rdaCodeByInvoice, $rdaRowsByCode, $feGlobalByDoc, $feByFornitoreDoc) {
$code = trim((string) ($row->cod_fornitore ?? ''));
$row->fornitore_nome = $fornitoriMap[$code] ?? null;
$row->fornitore_id = $fornitoriIdMap[$code] ?? null;
$tributoFromFornitore = null;
$codeNoZero = ltrim($code, '0');
if ($code !== '' && ! empty($fornitoriTributoMap[$code])) {
$tributoFromFornitore = $fornitoriTributoMap[$code];
} elseif ($codeNoZero !== '' && ! empty($fornitoriTributoMap[$codeNoZero])) {
$tributoFromFornitore = $fornitoriTributoMap[$codeNoZero];
}
$descrizione = trim((string) ($row->descrizione_sintetica ?? ''));
if ($descrizione === '') {
$descrizione = trim((string) ($row->descrizione ?? ''));
}
if ($descrizione === '') {
$descrizione = trim((string) ($row->descrizione_corpo ?? ''));
}
$row->descrizione_view = $descrizione !== '' ? $descrizione : '—';
$row->totale_view = $this->normalizeMoney($row->totale_fattura ?? $row->importo_netto ?? $row->imponibile ?? 0);
$row->ra_fatta_importo = abs($this->normalizeMoney($row->importo_rda ?? 0));
$row->ra_versata_data = null;
$row->ra_tributo = null;
$row->ra_riferimento = null;
$legacyId = is_numeric($row->legacy_id_fatture ?? null) ? (int) $row->legacy_id_fatture : null;
if ($row->ra_fatta_importo <= 0 && $legacyId && isset($legacyRitenutaById[$legacyId])) {
$row->ra_fatta_importo = abs($this->normalizeMoney($legacyRitenutaById[$legacyId]));
}
$row->local_match_id = null;
$row->local_match_url = null;
$row->local_match_stato = null;
$row->local_match_fe_id = null;
$row->local_match_fe_url = null;
$numero = $this->normalizeInvoiceNumber($row->numero_fattura ?? null);
$data = ! empty($row->data_fattura)
? (string) \Carbon\Carbon::parse($row->data_fattura)->format('Y-m-d')
: null;
if ($numero !== null) {
$fornCandidates = array_values(array_unique(array_filter([
$code,
ltrim($code, '0'),
], fn($v) => is_string($v) && $v !== '')));
foreach ($fornCandidates as $fornCode) {
$key = $fornCode . '|' . $numero;
if (isset($rdaCodeByInvoice[$key])) {
$row->ra_riferimento = $rdaCodeByInvoice[$key];
break;
}
}
}
if (empty($row->ra_riferimento) && ! empty($row->riferimento)) {
$row->ra_riferimento = trim((string) $row->riferimento);
}
if (! empty($row->ra_riferimento) && isset($rdaRowsByCode[$row->ra_riferimento])) {
$rdaData = $rdaRowsByCode[$row->ra_riferimento];
if ($row->ra_fatta_importo <= 0) {
$row->ra_fatta_importo = abs($this->normalizeMoney($rdaData['importo'] ?? 0));
}
if (empty($row->ra_versata_data) && ! empty($rdaData['data'])) {
$row->ra_versata_data = $rdaData['data'];
}
}
if ($row->fornitore_id && $numero && $data) {
$key = (int) $row->fornitore_id . '|' . $numero . '|' . $data;
$match = $fattureLocali[$key][0] ?? null;
if ($match) {
$row->local_match_id = (int) ($match->id ?? 0);
$row->local_match_stato = $match->stato ?? null;
$row->ra_versata_data = $match->data_versamento_ra ?? null;
$row->ra_tributo = $match->ritenuta_codice_tributo ?? null;
if ($row->ra_fatta_importo <= 0) {
$row->ra_fatta_importo = abs($this->normalizeMoney($match->ritenuta_importo ?? 0));
}
$row->local_match_url = \App\Filament\Pages\Contabilita\FatturaFornitoreScheda::getUrl([
'record' => (int) $match->id,
], panel: 'admin-filament');
$feId = is_numeric($match->fattura_elettronica_id ?? null) ? (int) $match->fattura_elettronica_id : 0;
if ($feId > 0) {
$row->local_match_fe_id = $feId;
$row->local_match_fe_url = \App\Filament\Pages\Contabilita\FatturaElettronicaScheda::getUrl([
'record' => $feId,
], panel: 'admin-filament');
}
}
}
// Fallback: FE presente ma fattura contabile non ancora agganciata.
if (empty($row->local_match_url) && $numero && $data) {
$feMatch = null;
if ($row->fornitore_id) {
$strictKey = (int) $row->fornitore_id . '|' . $numero . '|' . $data;
$strict = $feByFornitoreDoc[$strictKey] ?? [];
if (count($strict) === 1) {
$feMatch = $strict[0];
}
}
if (! $feMatch) {
$globalKey = $numero . '|' . $data;
$global = $feGlobalByDoc[$globalKey] ?? [];
if (count($global) === 1) {
$feMatch = $global[0];
}
}
if ($feMatch && is_numeric($feMatch->id ?? null)) {
$row->local_match_fe_id = (int) $feMatch->id;
$row->local_match_fe_url = \App\Filament\Pages\Contabilita\FatturaElettronicaScheda::getUrl([
'record' => (int) $feMatch->id,
], panel: 'admin-filament');
}
}
if (empty($row->ra_tributo) && ! empty($tributoFromFornitore)) {
$row->ra_tributo = $tributoFromFornitore;
}
if ($row->ra_fatta_importo > 0 && empty($row->ra_tributo)) {
$row->ra_tributo = '1040';
}
return $row;
})
);
return $paginator;
}
private function resolveTributoByFornitoreIdMap(array $fornitoreIds): array
{
if (empty($fornitoreIds) || ! Schema::hasTable('fornitori') || ! Schema::hasTable('stg_fornitori_gescon')) {
return [];
}
$fornitoriRows = DB::table('fornitori')
->whereIn('id', $fornitoreIds)
->get(['id', 'old_id']);
$oldIds = $fornitoriRows
->pluck('old_id')
->filter(fn($v) => $v !== null && trim((string) $v) !== '')
->map(fn($v) => trim((string) $v))
->unique()
->values()
->all();
if (empty($oldIds)) {
return [];
}
$stgRows = DB::table('stg_fornitori_gescon')
->whereIn('cod_forn', $oldIds)
->get(['cod_forn', 'raw']);
$tributoByCodForn = [];
foreach ($stgRows as $stg) {
$cod = trim((string) ($stg->cod_forn ?? ''));
if ($cod === '') {
continue;
}
$raw = json_decode((string) ($stg->raw ?? ''), true);
if (! is_array($raw)) {
continue;
}
$tributo = strtoupper(trim((string) ($raw['trib_1019_1020'] ?? '')));
if ($tributo !== '') {
$tributoByCodForn[$cod] = $tributo;
$codNoZero = ltrim($cod, '0');
if ($codNoZero !== '') {
$tributoByCodForn[$codNoZero] = $tributo;
}
}
}
$out = [];
foreach ($fornitoriRows as $fornitore) {
$oldId = trim((string) ($fornitore->old_id ?? ''));
if ($oldId === '') {
continue;
}
$oldIdNoZero = ltrim($oldId, '0');
$tributo = $tributoByCodForn[$oldId] ?? ($oldIdNoZero !== '' ? ($tributoByCodForn[$oldIdNoZero] ?? null) : null);
if ($tributo) {
$out[(int) $fornitore->id] = $tributo;
}
}
return $out;
}
public function importaFattureLegacy(): void
{
$user = Auth::user();
if (! $user) {
abort(403);
}
if (! Schema::hasTable('contabilita_fatture_fornitori')) {
Notification::make()
->title('Tabella fatture fornitori non disponibile')
->danger()
->send();
return;
}
$activeStabileId = StabileContext::resolveActiveStabileId($user);
if (! $activeStabileId) {
Notification::make()
->title('Seleziona uno stabile attivo')
->warning()
->send();
return;
}
$tenantId = null;
if (Schema::hasTable('stabili')) {
$tenantId = DB::table('stabili')
->where('id', (int) $activeStabileId)
->value('amministratore_id');
}
if (! $tenantId && isset($user->amministratore_id) && is_numeric($user->amministratore_id)) {
$tenantId = (int) $user->amministratore_id;
}
if (! $tenantId) {
Notification::make()
->title('Amministratore non risolto')
->body('Impossibile risolvere il tenant per l\'import fatture.')
->danger()
->send();
return;
}
$params = [
'tenant' => (string) $tenantId,
'--stabile-id' => (string) $activeStabileId,
];
$legacyCode = $this->resolveLegacyCodiceStabile();
if (! empty($legacyCode)) {
$params['--stabile-code'] = $legacyCode;
}
if ($this->filterAnno) {
$params['--anno'] = (string) $this->filterAnno;
}
try {
$exit = Artisan::call('gescon:sync-fatture-fornitori', $params);
$output = trim((string) Artisan::output());
} catch (\Throwable $e) {
Notification::make()
->title('Import fatture fallito')
->body($e->getMessage())
->danger()
->send();
return;
}
if ($exit === 0 && str_contains($output, 'Nessuna fattura da sincronizzare.')) {
$mdbPath = $this->resolveLegacyGeneraleMdbPath((int) $activeStabileId);
if (! empty($mdbPath)) {
try {
$loadExit = Artisan::call('import:gescon-fatture-legacy', [
'tenant' => (string) $tenantId,
'--mdb' => $mdbPath,
]);
$loadOutput = trim((string) Artisan::output());
} catch (\Throwable $e) {
Notification::make()
->title('Import da generale_stabile.mdb fallito')
->body($e->getMessage())
->danger()
->send();
return;
}
if ($loadExit !== 0) {
$snippet = $loadOutput !== '' ? mb_substr($loadOutput, 0, 800) : 'Errore durante caricamento MDB.';
Notification::make()
->title('Import da generale_stabile.mdb: errore')
->body($snippet)
->danger()
->send();
return;
}
try {
$exit = Artisan::call('gescon:sync-fatture-fornitori', $params);
$output = trim((string) Artisan::output());
} catch (\Throwable $e) {
Notification::make()
->title('Sync post-import fallita')
->body($e->getMessage())
->danger()
->send();
return;
}
}
}
if ($exit !== 0) {
$snippet = $output !== '' ? mb_substr($output, 0, 800) : 'Errore durante la sincronizzazione.';
Notification::make()
->title('Import fatture: errore')
->body($snippet)
->danger()
->send();
return;
}
$tail = $output !== '' ? trim((string) collect(preg_split('/\R/', $output) ?: [])->last()): null;
Notification::make()
->title('Import fatture completato')
->body($tail ?: 'Sincronizzazione eseguita correttamente.')
->success()
->send();
$this->resetPage();
}
private function resolveLegacyGeneraleMdbPath(int $stabileId): ?string
{
$base = (string) UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$base = rtrim($base, '/');
if ($base === '') {
$base = '/mnt/gescon-archives/gescon';
}
$stabile = Schema::hasTable('stabili')
? DB::table('stabili')->where('id', $stabileId)->first(['codice_stabile', 'cod_stabile', 'old_id'])
: null;
$codes = [];
foreach ([(string) ($stabile->codice_stabile ?? ''), (string) ($stabile->cod_stabile ?? ''), (string) ($stabile->old_id ?? '')] as $raw) {
$value = trim($raw);
if ($value === '') {
continue;
}
$codes[] = $value;
$codes[] = ltrim($value, '0');
if (is_numeric($value)) {
$num = (int) $value;
$codes[] = (string) $num;
$codes[] = str_pad((string) $num, 4, '0', STR_PAD_LEFT);
}
}
$codes = array_values(array_unique(array_filter($codes, fn($v) => is_string($v) && trim($v) !== '')));
foreach ($codes as $code) {
$path = $base . '/' . $code . '/generale_stabile.mdb';
if (is_file($path)) {
return $path;
}
}
return null;
}
private function applyIncassiFilters($query): void
{
$codStabile = $this->resolveLegacyCodiceStabile();
if ($codStabile && Schema::connection('gescon_import')->hasColumn('incassi', 'cod_stabile')) {
$query->where('incassi.cod_stabile', $codStabile);
if (Schema::connection('gescon_import')->hasColumn('incassi', 'legacy_year')) {
$hasTaggedRows = DB::connection('gescon_import')
->table('incassi')
->where('cod_stabile', $codStabile)
->whereNotNull('legacy_year')
->exists();
if ($hasTaggedRows) {
$query->whereNotNull('incassi.legacy_year');
}
}
}
if ($this->onlyStraordinarieMode) {
if (Schema::connection('gescon_import')->hasColumn('incassi', 'o_r_s')) {
$query->where('incassi.o_r_s', 'S');
}
if ($this->selectedStraordinaria !== null && Schema::connection('gescon_import')->hasColumn('incassi', 'n_stra')) {
$query->where('incassi.n_stra', $this->selectedStraordinaria);
}
} else {
if (Schema::connection('gescon_import')->hasColumn('incassi', 'o_r_s')) {
$query->where(function ($qq): void {
$qq->where('incassi.o_r_s', 'O')
->orWhereNull('incassi.o_r_s')
->orWhere('incassi.o_r_s', '');
});
}
}
$anno = $this->legacyGestioneAnno;
if ($anno) {
$annoCol = Schema::connection('gescon_import')->hasColumn('incassi', 'anno')
? 'anno'
: (Schema::connection('gescon_import')->hasColumn('incassi', 'anno_rif') ? 'anno_rif' : null);
$annoShort = (int) substr((string) $anno, -2);
$query->where(function ($qq) use ($annoCol, $anno, $annoShort): void {
if ($annoCol) {
$qq->where($annoCol, (string) $anno)
->orWhere($annoCol, (string) $annoShort)
->orWhere($annoCol, str_pad((string) $annoShort, 2, '0', STR_PAD_LEFT));
}
if (Schema::connection('gescon_import')->hasColumn('incassi', 'dt_empag')) {
$qq->orWhereYear('dt_empag', (int) $anno);
}
});
}
if (! empty($this->search)) {
$needle = trim((string) $this->search);
$query->where(function ($qq) use ($needle): void {
foreach (['cod_cond', 'descrizione', 'cod_cassa', 'n_ricevuta', 'n_riferimento', 'riferimento_pagamento'] as $col) {
if (Schema::connection('gescon_import')->hasColumn('incassi', $col)) {
$qq->orWhere($col, 'like', '%' . $needle . '%');
}
}
});
}
}
private function resolveIncassoImporto(object $row): float
{
$importoEuro = $this->normalizeMoney($row->importo_euro ?? null);
if (abs($importoEuro) > 0.00001) {
return round($importoEuro, 2);
}
$importo = $this->normalizeMoney($row->importo ?? null);
if (abs($importo) > 0.00001) {
return round($importo, 2);
}
$pagato = $this->normalizeMoney($row->importo_pagato_euro ?? null);
$abs = abs($pagato);
if ($abs >= 10000 && fmod($abs, 1.0) === 0.0) {
return round($pagato / 10000, 2);
}
if ($abs >= 1000 && fmod($abs, 1.0) === 0.0 && ((int) $abs % 100) === 0) {
return round($pagato / 100, 2);
}
return round($pagato, 2);
}
private function relationScoreForIncasso(string $tipoRelazione, string $ruolo): int
{
$tipo = strtolower(trim($tipoRelazione));
if ($ruolo === 'I') {
return match ($tipo) {
'inquilino', 'conduttore', 'affittuario', 'locatario' => 100,
default => 10,
};
}
return match ($tipo) {
'proprietario' => 100,
'comproprietario' => 90,
'usufruttuario' => 80,
'delegato_assemblea' => 70,
'referente' => 60,
default => 10,
};
}
private function findRateMatchForIncasso(object $incasso, array $rateRows): ?array
{
$refs = [];
foreach (['n_rata_riferimento', 'riferimento_pagamento', 'n_riferimento', 'n_ricevuta'] as $field) {
$v = $incasso->{$field} ?? null;
if (is_numeric($v)) {
$refs[] = (int) $v;
} elseif (is_string($v)) {
$trim = trim($v);
if (ctype_digit($trim)) {
$refs[] = (int) $trim;
}
}
}
$refs = array_values(array_unique($refs));
foreach ($rateRows as $rate) {
$nEmissione = is_numeric($rate->n_emissione ?? null) ? (int) $rate->n_emissione : null;
if ($nEmissione !== null && in_array($nEmissione, $refs, true)) {
return [
'n_emissione' => $nEmissione,
'importo' => (float) ($rate->importo_euro ?? $rate->importo ?? 0),
'data_emissione' => $rate->data_emissione ?? null,
'causale' => $rate->causale ?? null,
'tipo_rata' => $rate->tipo_rata ?? null,
'pagata' => (bool) ($rate->pagata ?? false),
];
}
}
$incassoImporto = abs($this->resolveIncassoImporto($incasso));
if ($incassoImporto <= 0.0) {
return null;
}
$best = null;
foreach ($rateRows as $rate) {
$rateImporto = abs($this->normalizeMoney($rate->importo_euro ?? $rate->importo ?? 0));
if (abs($incassoImporto - $rateImporto) > 0.01) {
continue;
}
$score = 10;
$incassoDate = ! empty($incasso->data_visuale) ? (string) $incasso->data_visuale : null;
$rateDate = ! empty($rate->data_scadenza) ? (string) $rate->data_scadenza : (! empty($rate->data_emissione) ? (string) $rate->data_emissione : null);
if ($incassoDate && $rateDate) {
try {
$d1 = \Carbon\Carbon::parse($incassoDate);
$d2 = \Carbon\Carbon::parse($rateDate);
$days = abs($d1->diffInDays($d2));
$score += max(0, 30 - min($days, 30));
} catch (\Throwable) {
}
}
if (! $best || $score > ($best['score'] ?? -1)) {
$best = [
'score' => $score,
'n_emissione' => is_numeric($rate->n_emissione ?? null) ? (int) $rate->n_emissione : null,
'importo' => (float) ($rate->importo_euro ?? $rate->importo ?? 0),
'data_emissione' => $rate->data_emissione ?? null,
'causale' => $rate->causale ?? null,
'tipo_rata' => $rate->tipo_rata ?? null,
'pagata' => (bool) ($rate->pagata ?? false),
];
}
}
if (! $best) {
return null;
}
unset($best['score']);
return $best;
}
private function formatScalaInternoShort($scala, $interno): string
{
$s = trim((string) ($scala ?? ''));
$i = trim((string) ($interno ?? ''));
if ($s !== '') {
$s = preg_replace('/^S/i', '', $s);
}
if ($i !== '') {
$i = preg_replace('/^I/i', '', $i);
}
if ($s !== '' && $i !== '') {
return $s . '-' . $i;
}
if ($s !== '') {
return $s;
}
if ($i !== '') {
return $i;
}
return '—';
}
public function openDettPersModal(int $nSpe): void
{
$legacyCode = $this->resolveLegacyCodiceStabile();
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
return;
}
$operazione = DB::connection('gescon_import')
->table('operazioni')
->where('n_spe', $nSpe)
->whereIn('cod_spe', ['I05', 'I06'])
->when(
$legacyCode && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile'),
fn($q) => $q->where('cod_stabile', $legacyCode)
)
->select(['importo_euro', 'importo', 'dt_spe', 'unico', 'gestione', 'n_stra'])
->first();
$dettCols = ['id_cond', 'cond_inq', 'importo', 'importo_euro'];
foreach (['unico', 'n_spe', 'n_stra', 'tipo_gestione', 'legacy_year'] as $optionalCol) {
if (Schema::connection('gescon_import')->hasColumn('dett_pers', $optionalCol)) {
$dettCols[] = $optionalCol;
}
}
$dettQuery = DB::connection('gescon_import')
->table('dett_pers');
$hasNspe = Schema::connection('gescon_import')->hasColumn('dett_pers', 'n_spe');
$hasNstra = Schema::connection('gescon_import')->hasColumn('dett_pers', 'n_stra');
$hasUnico = Schema::connection('gescon_import')->hasColumn('dett_pers', 'unico');
if ($hasNspe || $hasNstra || ($hasUnico && ! empty($operazione?->unico))) {
$dettQuery->where(function ($q) use ($nSpe, $operazione, $hasNspe, $hasNstra, $hasUnico): void {
if ($hasNspe) {
$q->orWhere('n_spe', $nSpe);
}
if ($hasNstra) {
$q->orWhere('n_stra', $nSpe);
}
if ($hasUnico && ! empty($operazione?->unico)) {
$q->orWhere('unico', $operazione->unico);
}
});
} else {
$dettQuery->whereRaw('1 = 0');
}
if (Schema::connection('gescon_import')->hasColumn('dett_pers', 'tipo_gestione') && ! empty($operazione?->gestione)) {
$dettQuery->where('tipo_gestione', $operazione->gestione);
}
if (Schema::connection('gescon_import')->hasColumn('dett_pers', 'legacy_year')) {
$dettQuery->whereIn('legacy_year', ['0001', (string) \Carbon\Carbon::now()->format('Y')]);
}
$dettRows = $dettQuery->get($dettCols);
$this->dettPersNSpe = $nSpe;
$this->dettPersImportoOperazione = $this->normalizeMoney($operazione->importo_euro ?? $operazione->importo ?? 0);
$this->dettPersLegacyCode = $this->resolveLegacyCodiceStabile();
$this->dettPersLegacyYear = '0001';
$legacyAmounts = [];
$legacyUnico = [];
foreach ($dettRows as $dett) {
$idCond = is_numeric($dett->id_cond ?? null) ? (int) $dett->id_cond : 0;
$role = strtoupper(trim((string) ($dett->cond_inq ?? 'C')));
$role = $role === 'I' ? 'I' : 'C';
if ($idCond <= 0) {
continue;
}
$key = $idCond . '|' . $role;
$legacyAmounts[$key] = ($legacyAmounts[$key] ?? 0.0) + $this->normalizeMoney($dett->importo_euro ?? $dett->importo ?? 0);
if (isset($dett->unico) && is_numeric($dett->unico)) {
$legacyUnico[(int) $dett->unico] = true;
}
}
$this->dettPersLegacyAmounts = $legacyAmounts;
$this->dettPersLegacyUnico = array_values(array_map('intval', array_keys($legacyUnico)));
sort($this->dettPersLegacyUnico);
$recipients = [];
$targetOptions = [];
$legacyCode = $this->dettPersLegacyCode;
if ($legacyCode) {
$condQuery = DB::connection('gescon_import')
->table('condomin')
->where('cod_stabile', $legacyCode);
$condColumns = ['cod_cond', 'scala', 'interno', 'piano', 'cognome', 'nome', 'denominazione', 'proprietario_denominazione', 'inquilino_denominazione', 'inquil_nome'];
$condominCols = Schema::connection('gescon_import')->getColumnListing('condomin');
$condColumns = array_intersect($condColumns, $condominCols);
if (Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year') && $this->dettPersLegacyYear) {
$condQuery->whereIn('legacy_year', [$this->dettPersLegacyYear, '0001']);
$condColumns[] = 'legacy_year';
}
$condRows = $condQuery->get(array_values(array_unique($condColumns)));
$byId = [];
foreach ($condRows as $cond) {
$idCond = is_numeric($cond->cod_cond ?? null) ? (int) $cond->cod_cond : null;
if (! $idCond || isset($byId[$idCond])) {
continue;
}
$byId[$idCond] = $cond;
}
foreach ($byId as $idCond => $cond) {
$unita = trim(implode(' ', array_filter([
! empty($cond->scala) ? 'Scala ' . $cond->scala : null,
! empty($cond->interno) ? 'Int. ' . $cond->interno : null,
! empty($cond->piano) ? 'P. ' . $cond->piano : null,
])));
$propName = trim((string) ($cond->proprietario_denominazione ?? ''));
if ($propName === '') {
$propName = trim((string) (($cond->cognome ?? '') . ' ' . ($cond->nome ?? '')));
}
if ($propName === '') {
$propName = trim((string) ($cond->denominazione ?? ''));
}
$inquName = trim((string) ($cond->inquilino_denominazione ?? ''));
if ($inquName === '') {
$inquName = trim((string) ($cond->inquil_nome ?? ''));
}
$keyC = $idCond . '|C';
$recipients[$keyC] = [
'key' => $keyC,
'id_cond' => $idCond,
'cond_inq' => 'C',
'soggetto' => 'Condomino: ' . ($propName !== '' ? $propName : ('Cond. ' . $idCond)),
'unita' => $unita !== '' ? $unita : '—',
];
$targetOptions[$keyC] = ($unita !== '' ? ($unita . ' · ') : '') . 'Condomino ' . ($propName !== '' ? $propName : ('Cond. ' . $idCond));
if ($inquName !== '') {
$keyI = $idCond . '|I';
$recipients[$keyI] = [
'key' => $keyI,
'id_cond' => $idCond,
'cond_inq' => 'I',
'soggetto' => 'Inquilino: ' . $inquName,
'unita' => $unita !== '' ? $unita : '—',
];
$targetOptions[$keyI] = ($unita !== '' ? ($unita . ' · ') : '') . 'Inquilino ' . $inquName;
}
}
}
foreach ($legacyAmounts as $key => $amount) {
if (isset($recipients[$key])) {
continue;
}
[$idCond, $role] = array_pad(explode('|', $key), 2, 'C');
$idCond = is_numeric($idCond) ? (int) $idCond : 0;
$role = strtoupper($role) === 'I' ? 'I' : 'C';
$recipients[$key] = [
'key' => $key,
'id_cond' => $idCond,
'cond_inq' => $role,
'soggetto' => ($role === 'I' ? 'Inquilino' : 'Condomino') . ': Cond. ' . ($idCond > 0 ? $idCond : '—'),
'unita' => '—',
];
$targetOptions[$key] = 'Cond. ' . ($idCond > 0 ? $idCond : '—') . ' (' . ($role === 'I' ? 'Inquilino' : 'Condomino') . ')';
}
ksort($recipients);
$this->dettPersRecipients = array_values($recipients);
$this->dettPersRipartoTargetOptions = $targetOptions;
$this->dettPersRipartoTarget = array_key_first($targetOptions);
$tabOptions = [];
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
if ($activeStabileId && Schema::hasTable('tabelle_millesimali') && Schema::hasTable('dettaglio_millesimi')) {
$localRows = DB::table('tabelle_millesimali')
->where('stabile_id', (int) $activeStabileId)
->orderBy('codice_tabella')
->get(['id', 'codice_tabella', 'nome_tabella', 'denominazione']);
foreach ($localRows as $localRow) {
$key = 'LOCAL:' . (int) $localRow->id;
$label = trim((string) ($localRow->codice_tabella ?? 'TAB'));
$name = trim((string) ($localRow->denominazione ?? $localRow->nome_tabella ?? ''));
if ($name !== '') {
$label .= ' · ' . $name;
}
$sum = (float) DB::table('dettaglio_millesimi')
->where('tabella_millesimale_id', (int) $localRow->id)
->where('partecipa', true)
->sum('millesimi');
$tabOptions[$key] = $label . ' (totale ' . number_format($sum, 3, ',', '.') . ')';
}
}
if ($legacyCode && Schema::connection('gescon_import')->hasTable('dett_tab')) {
$tabQuery = DB::connection('gescon_import')
->table('dett_tab')
->where('cod_stabile', $legacyCode)
->select('cod_tab')
->distinct();
if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
$tabQuery->whereIn('legacy_year', ['0001', (string) \Carbon\Carbon::now()->format('Y')]);
}
$tabs = $tabQuery->orderBy('cod_tab')->pluck('cod_tab')->all();
foreach ($tabs as $tab) {
$code = strtoupper(trim((string) $tab));
if ($code === '') {
continue;
}
$tabOptions[$code] = ($tabOptions[$code] ?? $code);
}
}
$this->dettPersRipartoTabellaOptions = $tabOptions;
$this->dettPersRipartoTabella = array_key_first($tabOptions);
$this->dettPersRipartoMode = 'legacy';
$this->rebuildDettPersRows();
}
public function updatedDettPersRipartoMode(): void
{
$this->rebuildDettPersRows();
}
public function updatedDettPersRipartoTabella(): void
{
$this->rebuildDettPersRows();
}
public function updatedDettPersRipartoTarget(): void
{
$this->rebuildDettPersRows();
}
public function updatedDettPersRows($value = null, $key = null): void
{
if ($this->dettPersRipartoMode !== 'riparto_libero') {
return;
}
$this->recalculateDettPersTotalsFromRows();
}
private function rebuildDettPersRows(): void
{
$recipients = $this->dettPersRecipients;
$total = $this->dettPersImportoOperazione;
$amounts = [];
foreach ($recipients as $recipient) {
$key = (string) ($recipient['key'] ?? '');
if ($key !== '') {
$amounts[$key] = 0.0;
}
}
$mode = $this->dettPersRipartoMode;
if ($mode === 'parti_uguali') {
$keys = array_keys($amounts);
$distributed = $this->distributeEqually($total, $keys);
foreach ($distributed as $key => $value) {
$amounts[$key] = $value;
}
} elseif ($mode === 'riparto_libero') {
foreach ($this->dettPersRows as $row) {
$idCond = is_numeric($row['id_cond'] ?? null) ? (int) $row['id_cond'] : 0;
$role = strtoupper((string) ($row['cond_inq'] ?? 'C')) === 'I' ? 'I' : 'C';
if ($idCond <= 0) {
continue;
}
$key = $idCond . '|' . $role;
if (! array_key_exists($key, $amounts)) {
continue;
}
$amounts[$key] = round($this->normalizeMoney($row['importo'] ?? 0), 2);
}
} elseif ($mode === 'unita_singola') {
$target = (string) ($this->dettPersRipartoTarget ?? '');
if ($target !== '' && array_key_exists($target, $amounts)) {
$amounts[$target] = round($total, 2);
}
} elseif ($mode === 'millesimi_tabella') {
$weights = $this->resolveMillesimiWeights();
if (! empty($weights)) {
$distributed = $this->distributeByWeights($total, $weights);
foreach ($distributed as $key => $value) {
if (array_key_exists($key, $amounts)) {
$amounts[$key] = $value;
}
}
} else {
foreach ($this->dettPersLegacyAmounts as $key => $value) {
if (array_key_exists($key, $amounts)) {
$amounts[$key] = round((float) $value, 2);
}
}
}
} else {
foreach ($this->dettPersLegacyAmounts as $key => $value) {
if (array_key_exists($key, $amounts)) {
$amounts[$key] = round((float) $value, 2);
}
}
}
$rows = [];
$totale = 0.0;
foreach ($recipients as $recipient) {
$key = (string) ($recipient['key'] ?? '');
$value = round((float) ($amounts[$key] ?? 0), 2);
$totale += $value;
$rows[] = [
'id_cond' => $recipient['id_cond'] ?? null,
'soggetto' => $recipient['soggetto'] ?? '—',
'unita' => $recipient['unita'] ?? '—',
'cond_inq' => $recipient['cond_inq'] ?? 'C',
'importo' => $value,
];
}
$this->dettPersRows = $rows;
$this->dettPersTotale = round($totale, 2);
$this->dettPersDelta = round($this->dettPersImportoOperazione - $this->dettPersTotale, 2);
$this->dettPersQuadraturaOk = abs($this->dettPersDelta) <= 0.01;
}
private function distributeEqually(float $total, array $keys): array
{
$out = [];
if (empty($keys)) {
return $out;
}
$count = count($keys);
$base = round($total / $count, 2);
foreach ($keys as $key) {
$out[$key] = $base;
}
$sum = array_sum($out);
$delta = round($total - $sum, 2);
if ($delta !== 0.0) {
$firstKey = $keys[0];
$out[$firstKey] = round(($out[$firstKey] ?? 0) + $delta, 2);
}
return $out;
}
private function distributeByWeights(float $total, array $weights): array
{
$sumWeights = array_sum($weights);
if ($sumWeights <= 0) {
return [];
}
$out = [];
foreach ($weights as $key => $weight) {
$share = ($total * ((float) $weight / $sumWeights));
$out[$key] = round($share, 2);
}
$sum = array_sum($out);
$delta = round($total - $sum, 2);
if ($delta !== 0.0) {
$firstKey = array_key_first($out);
if ($firstKey !== null) {
$out[$firstKey] = round(($out[$firstKey] ?? 0) + $delta, 2);
}
}
return $out;
}
private function resolveMillesimiWeights(): array
{
$tabKey = trim((string) ($this->dettPersRipartoTabella ?? ''));
if ($tabKey === '') {
return [];
}
if (str_starts_with($tabKey, 'LOCAL:') && Schema::hasTable('dettaglio_millesimi') && Schema::hasTable('unita_immobiliari')) {
$tabellaId = (int) str_replace('LOCAL:', '', $tabKey);
if ($tabellaId <= 0) {
return [];
}
$rows = DB::table('dettaglio_millesimi as dm')
->join('unita_immobiliari as ui', 'ui.id', '=', 'dm.unita_immobiliare_id')
->where('dm.tabella_millesimale_id', $tabellaId)
->where('dm.partecipa', true)
->get(['ui.legacy_cond_id', 'dm.millesimi']);
$weights = [];
foreach ($rows as $row) {
$idCond = is_numeric($row->legacy_cond_id ?? null) ? (int) $row->legacy_cond_id : 0;
if ($idCond <= 0) {
continue;
}
$w = $this->normalizeMoney($row->millesimi ?? 0);
if ($w <= 0) {
continue;
}
$key = $idCond . '|C';
$weights[$key] = ($weights[$key] ?? 0.0) + $w;
}
return $weights;
}
$legacyCode = $this->dettPersLegacyCode;
$codTab = strtoupper($tabKey);
if (! $legacyCode || ! Schema::connection('gescon_import')->hasTable('dett_tab')) {
return [];
}
$query = DB::connection('gescon_import')
->table('dett_tab')
->where('cod_stabile', $legacyCode)
->where('cod_tab', $codTab);
if (Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
$query->whereIn('legacy_year', ['0001', (string) \Carbon\Carbon::now()->format('Y')]);
}
$rows = $query->get(['id_cond', 'cond_inquil', 'cons_euro', 'mm', 'prev_euro']);
$weights = [];
foreach ($rows as $row) {
$idCond = is_numeric($row->id_cond ?? null) ? (int) $row->id_cond : 0;
$role = strtoupper(trim((string) ($row->cond_inquil ?? 'C')));
$role = $role === 'I' ? 'I' : 'C';
if ($idCond <= 0) {
continue;
}
$weight = $this->normalizeMoney($row->cons_euro ?? 0);
if ($weight <= 0) {
$weight = $this->normalizeMoney($row->mm ?? 0);
}
if ($weight <= 0) {
$weight = $this->normalizeMoney($row->prev_euro ?? 0);
}
if ($weight <= 0) {
continue;
}
$key = $idCond . '|' . $role;
$weights[$key] = ($weights[$key] ?? 0.0) + $weight;
}
return $weights;
}
private function recalculateDettPersTotalsFromRows(): void
{
$totale = 0.0;
foreach ($this->dettPersRows as $idx => $row) {
$value = round($this->normalizeMoney($row['importo'] ?? 0), 2);
$this->dettPersRows[$idx]['importo'] = $value;
$totale += $value;
}
$this->dettPersTotale = round($totale, 2);
$this->dettPersDelta = round($this->dettPersImportoOperazione - $this->dettPersTotale, 2);
$this->dettPersQuadraturaOk = abs($this->dettPersDelta) <= 0.01;
}
private function normalizeMoney($value): float
{
if ($value === null || $value === '') {
return 0.0;
}
$s = trim((string) $value);
$s = str_replace([' ', "\r", "\n", "\t"], '', $s);
if (str_contains($s, ',') && str_contains($s, '.')) {
$s = str_replace('.', '', $s);
$s = str_replace(',', '.', $s);
} elseif (str_contains($s, ',')) {
$s = str_replace(',', '.', $s);
}
return is_numeric($s) ? (float) $s : 0.0;
}
private function normalizeInvoiceNumber($value): ?string
{
$raw = strtoupper(trim((string) ($value ?? '')));
if ($raw === '') {
return null;
}
$normalized = preg_replace('/[^A-Z0-9]/', '', $raw);
if (! is_string($normalized) || $normalized === '') {
return null;
}
return $normalized;
}
private function computeRdaDueDate(?string $paymentDate): ?string
{
if (! $paymentDate) {
return null;
}
try {
$due = \Carbon\Carbon::parse($paymentDate)
->startOfMonth()
->addMonthNoOverflow()
->day(16)
->startOfDay();
while ($this->isNonWorkingDay($due)) {
$due->addDay();
}
return $due->format('Y-m-d');
} catch (\Throwable) {
return null;
}
}
private function isNonWorkingDay(\Carbon\Carbon $day): bool
{
if ($day->isWeekend()) {
return true;
}
$fixed = [
'01-01',
'01-06',
'04-25',
'05-01',
'06-02',
'08-15',
'11-01',
'12-08',
'12-25',
'12-26',
];
if (in_array($day->format('m-d'), $fixed, true)) {
return true;
}
$year = (int) $day->format('Y');
$easter = \Carbon\Carbon::createFromTimestamp(easter_date($year))->startOfDay();
$easterMonday = $easter->copy()->addDay();
return $day->isSameDay($easter) || $day->isSameDay($easterMonday);
}
private function buildOperazioniQuery()
{
if ($this->netgesconWorkflowMode) {
$query = DB::table('operazioni_contabili as o')
->join('gestioni_contabili as g', 'g.id', '=', 'o.gestione_id');
$activeStabileId = StabileContext::resolveActiveStabileId(Auth::user());
if ($activeStabileId) {
$query->where('g.stabile_id', $activeStabileId);
}
if ($this->onlyStraordinarieMode) {
$query->where('g.tipo_gestione', 'straordinaria');
if ($this->selectedStraordinaria !== null) {
$query->where('g.numero_straordinaria', $this->selectedStraordinaria);
}
} elseif ($this->onlyRiscaldamentoMode) {
$query->where('g.tipo_gestione', 'riscaldamento');
} else {
$query->where('g.tipo_gestione', 'ordinaria');
}
if ($this->search) {
$s = '%' . $this->search . '%';
$query->where(function ($q) use ($s) {
$q->where('o.descrizione', 'like', $s)
->orWhere('o.conto_contabile', 'like', $s)
->orWhere('o.protocollo_completo', 'like', $s)
->orWhere('o.num_fat', 'like', $s);
});
}
if ($this->filterAnno) {
$query->where('g.anno_gestione', (int) $this->filterAnno);
}
$query->select([
'o.id as id_operaz',
'o.protocollo_numero as n_spe',
'o.data_operazione as dt_spe',
'g.anno_gestione as legacy_year',
DB::raw("case when g.tipo_gestione = 'ordinaria' then 'O' when g.tipo_gestione = 'riscaldamento' then 'R' else 'S' end as gestione"),
'o.compet',
'o.conto_contabile as cod_spe',
DB::raw("null as tabella"),
DB::raw("null as cod_for"),
'o.descrizione as benef',
'o.n_stra',
'o.protocollo_completo',
DB::raw("coalesce(o.dare, o.avere, 0) as importo_euro"),
'o.num_fat',
'o.fe_uid',
'o.voce_spesa_snapshot',
'o.fornitore_snapshot',
]);
return $query;
}
if (! Schema::connection('gescon_import')->hasTable('operazioni')) {
return DB::table('operazioni_contabili as o')
->leftJoin('gestioni_contabili as g', 'g.id', '=', 'o.gestione_id')
->whereRaw('1 = 0')
->select([
'o.id as id_operaz',
'o.protocollo_numero as n_spe',
'o.data_operazione as dt_spe',
'g.anno_gestione as legacy_year',
DB::raw("case when g.tipo_gestione = 'ordinaria' then 'O' when g.tipo_gestione = 'riscaldamento' then 'R' else 'S' end as gestione"),
'o.compet',
'o.conto_contabile as cod_spe',
DB::raw("null as tabella"),
DB::raw("null as cod_for"),
'o.descrizione as benef',
'o.n_stra',
'o.protocollo_completo',
DB::raw("coalesce(o.dare, o.avere, 0) as importo_euro"),
DB::raw("coalesce(o.dare, o.avere, 0) as importo"),
'o.num_fat',
'o.fe_uid',
'o.voce_spesa_snapshot',
'o.fornitore_snapshot',
DB::raw("0 as importo_spese"),
DB::raw("0 as importo_entrate"),
DB::raw("0 as importo_debiti"),
DB::raw("0 as importo_crediti"),
DB::raw("null as natura2"),
]);
}
$query = DB::connection('gescon_import')->table('operazioni');
$legacyCode = $this->resolveLegacyCodiceStabile();
if ($legacyCode && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile')) {
$query->where('cod_stabile', $legacyCode);
}
if ($this->onlyStraordinarieMode) {
$query->where('gestione', 'S');
if ($this->selectedStraordinaria !== null) {
$query->where('n_stra', $this->selectedStraordinaria);
}
} elseif ($this->onlyRiscaldamentoMode) {
$query->where('gestione', 'R');
} elseif ($this->netgesconWorkflowMode) {
$query->where(function ($q) {
$q->where('gestione', 'O')
->orWhereNull('gestione')
->orWhere('gestione', '');
});
}
if (in_array($this->operazioniGestioneFilter, ['O', 'R', 'S'], true)) {
$query->where('gestione', $this->operazioniGestioneFilter);
}
if ($this->search) {
$s = '%' . $this->search . '%';
$query->where(function ($q) use ($s) {
$q->where('cod_for', 'like', $s)
->orWhere('cod_spe', 'like', $s)
->orWhere('benef', 'like', $s)
->orWhere('num_fat', 'like', $s)
->orWhere('fe_uid', 'like', $s)
->orWhere('rif_rda', 'like', $s)
->orWhere('id_operaz', 'like', $s)
->orWhere('n_spe', 'like', $s)
->orWhere('n_stra', 'like', $s);
});
}
if ($this->filterCodForn) {
$query->where('cod_for', $this->filterCodForn);
}
if ($this->filterCodSpe) {
$query->where('cod_spe', $this->filterCodSpe);
}
if ($this->filterTabella) {
$query->where('tabella', $this->filterTabella);
}
if ($this->filterBenef) {
$query->where('benef', 'like', '%' . $this->filterBenef . '%');
}
if ($this->filterRdaOnly) {
$query->where(function ($q) {
$q->whereNotNull('rif_rda')
->orWhere('benef', 'like', 'RDA:%');
});
}
if ($this->filterAnno) {
$legacyCode = $this->resolveLegacyCodiceStabile();
$matchingCartelle = [];
if ($legacyCode && Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
$matchingCartelle = DB::connection('gescon_import')
->table('gestioni_annuali')
->where('cod_stabile', $legacyCode)
->get(['cartella', 'anno_ordinario', 'anno_riscaldamento'])
->filter(function ($row): bool {
return $this->extractLegacyYearInt((string) ($row->anno_ordinario ?? '')) === (int) $this->filterAnno
|| $this->extractLegacyYearInt((string) ($row->anno_riscaldamento ?? '')) === (int) $this->filterAnno;
})
->map(fn($row) => trim((string) ($row->cartella ?? '')))
->filter()
->values()
->all();
}
if ($matchingCartelle !== [] && Schema::connection('gescon_import')->hasColumn('operazioni', 'legacy_year')) {
$query->whereIn('legacy_year', $matchingCartelle);
} else {
$query->whereYear('dt_spe', $this->filterAnno);
}
}
return $query;
}
private function computeSignedAmount($row): float
{
$base = $this->normalizeMoney($row->importo_euro ?? $row->importo ?? 0);
$spese = $this->normalizeMoney($row->importo_spese ?? 0);
$entrate = $this->normalizeMoney($row->importo_entrate ?? 0);
$debiti = $this->normalizeMoney($row->importo_debiti ?? 0);
$crediti = $this->normalizeMoney($row->importo_crediti ?? 0);
if (($spese + $entrate + $debiti + $crediti) > 0) {
return ($spese + $debiti) - ($entrate + $crediti);
}
$natura = strtoupper(trim((string) ($row->natura2 ?? '')));
if ($natura !== '' && in_array($natura, ['E', 'ENTRATA', 'ENTRATE', 'CREDITO'], true)) {
return -1 * $base;
}
if (strtoupper((string) ($row->cod_spe ?? '')) === 'RB1') {
return -1 * $base;
}
return $base;
}
public function openMastrino(string $tabella, string $codSpe): void
{
$this->mastrinoTabella = $tabella;
$this->mastrinoCodSpe = $codSpe;
$query = $this->buildOperazioniQuery()
->where('cod_spe', $codSpe)
->orderBy('dt_spe')
->orderBy('id_operaz');
if (trim($tabella) !== '') {
$query->where('tabella', $tabella);
}
if ($this->filterAnno) {
$query->whereYear('dt_spe', $this->filterAnno);
}
$rows = $query->get();
$this->mastrinoRows = $rows->map(function ($row) {
return [
'id' => $row->id_operaz ?? null,
'data' => $row->dt_spe ?? null,
'descrizione' => $row->des_oper ?? $row->benef ?? null,
'importo' => $this->computeSignedAmount($row),
'n_spe' => $row->n_spe ?? null,
'n_stra' => $row->n_stra ?? null,
'tabella' => $row->tabella ?? null,
'fe_uid' => $row->fe_uid ?? null,
'rif_rda' => $row->rif_rda ?? null,
'ra_importo' => $row->netto_vers_rda ?? $row->importo_euro_770 ?? null,
];
})->all();
}
public function sortBy(string $field): void
{
$allowed = [
'id_operaz',
'dt_spe',
'cod_spe',
'tabella',
'cod_for',
'benef',
'importo_euro',
'num_fat',
'fe_uid',
'rif_rda',
];
if (! in_array($field, $allowed, true)) {
return;
}
if ($this->sortField === $field) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortField = $field;
$this->sortDirection = 'asc';
}
}
public function getSpecialCodesSummaryProperty(): array
{
$base = $this->buildOperazioniQuery();
$rows = $base
->whereIn('cod_spe', ['Z01', 'Z02'])
->select('cod_spe')
->selectRaw("SUM(
CASE
WHEN (COALESCE(importo_spese, 0) + COALESCE(importo_entrate, 0) + COALESCE(importo_debiti, 0) + COALESCE(importo_crediti, 0)) = 0 THEN
CASE
WHEN natura2 IN ('E', 'ENTRATA', 'ENTRATE', 'CREDITO') THEN -1 * COALESCE(importo_euro, importo, 0)
ELSE COALESCE(importo_euro, importo, 0)
END
ELSE (COALESCE(importo_spese, 0) + COALESCE(importo_debiti, 0) - COALESCE(importo_entrate, 0) - COALESCE(importo_crediti, 0))
END
) as totale")
->groupBy('cod_spe')
->get();
$map = $rows->mapWithKeys(fn($row) => [strtoupper((string) $row->cod_spe) => (float) ($row->totale ?? 0)])->all();
return [
'Z01' => (float) ($map['Z01'] ?? 0),
'Z02' => (float) ($map['Z02'] ?? 0),
'totale' => (float) (($map['Z01'] ?? 0) + ($map['Z02'] ?? 0)),
];
}
private function applyOperazioniSorting($query)
{
$dir = strtolower($this->sortDirection) === 'asc' ? 'asc' : 'desc';
$field = $this->sortField;
if ($this->netgesconWorkflowMode) {
$mappedField = match ($field) {
'id_operaz' => 'o.id',
'dt_spe' => 'o.data_operazione',
'cod_spe' => 'o.conto_contabile',
'importo_euro' => 'o.dare',
'num_fat' => 'o.num_fat',
'fe_uid' => 'o.fe_uid',
default => 'o.id'
};
$query->orderBy($mappedField, $dir);
if ($field !== 'id_operaz') {
$query->orderBy('o.id', 'desc');
}
return $query;
}
switch ($field) {
case 'importo_euro':
$query->orderByRaw('COALESCE(importo_euro, importo, 0) ' . $dir);
break;
default:
$query->orderBy($field, $dir);
break;
}
if ($field !== 'id_operaz') {
$query->orderBy('id_operaz', 'desc');
}
return $query;
}
public function riallineaScrittureLegacy(): void
{
$activeStabileId = \App\Support\StabileContext::resolveActiveStabileId(\Illuminate\Support\Facades\Auth::user());
if (! $activeStabileId) {
\Filament\Notifications\Notification::make()->title('Nessuno stabile attivo')->danger()->send();
return;
}
try {
$params = [
'--stabile-id' => $activeStabileId,
'--tipo' => 'all',
];
if ($this->filterAnno) {
$params['--year'] = (int) $this->filterAnno;
}
\Illuminate\Support\Facades\Artisan::call('gescon:sync-operazioni-prima-nota', $params);
\Filament\Notifications\Notification::make()
->title('Riallineamento completato')
->body('Le scritture contabili legacy sono state riallineate con successo.')
->success()
->send();
$this->resetPage();
} catch (\Throwable $e) {
\Filament\Notifications\Notification::make()
->title('Errore durante il riallineamento')
->body($e->getMessage())
->danger()
->send();
}
}
}