netgescon-day0/resources/views/filament/pages/contabilita/casse-banche-movimenti.blade.php

427 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<x-filament-panels::page>
<div class="mx-auto max-w-7xl space-y-4">
<x-filament.components.page-breadcrumbs
:breadcrumbs="[
['label' => 'Contabilità', 'url' => null],
['label' => 'Casse e banche', 'url' => null],
]"
/>
@php
$stabile = $this->getActiveStabile();
$contiRows = $this->getHubContiRows();
$contiOptions = $this->getContiImportabili();
$hdr = $this->getHeaderSaldoInfo();
$periodo = $this->periodoTotali;
$periodoOptions = $this->periodoOptions;
$periodoRateOptions = $this->periodoRateOptions;
$periodoRate = $this->periodoRateTotali;
$contoImport = $this->getSelectedContoImportInfo();
$snapshotCards = $this->getSaldoSnapshotCards();
$saldoArchivio = $this->getSaldoArchivioByYear();
$ricStats = $this->getRiconciliazioneStats();
$ricQueue = $this->getRiconciliazioneQueue();
$ricCurrent = $this->getRiconciliazioneCurrent();
$ricCandidates = $this->getRiconciliazioneCandidates();
@endphp
@if(! $stabile)
<x-filament::section>
<div class="text-sm text-gray-600">Seleziona uno stabile per importare e verificare i movimenti banca.</div>
</x-filament::section>
@else
<x-filament::section>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1.4fr)_minmax(320px,0.8fr)]">
<div>
<div class="text-sm text-gray-500">Stabile attivo</div>
<div class="text-lg font-semibold text-gray-900">
{{ $stabile->codice_operatore ?? $stabile->codice_stabile ?? '—' }} {{ $stabile->denominazione ?? 'Stabile' }}
</div>
<div class="mt-1 text-xs text-gray-600">Hub operativo del conto corrente: conti, saldi ufficiali, movimenti importati e riconciliazione.</div>
</div>
<div class="rounded-xl border bg-slate-50 p-4">
<div class="text-xs font-medium uppercase tracking-wide text-slate-500">Conto attivo</div>
<div class="mt-2">
<select wire:model.live="contoId" class="w-full rounded-lg border-gray-300 text-sm">
<option value="">Seleziona conto</option>
@foreach($contiOptions as $conto)
<option value="{{ $conto['id'] }}">{{ $conto['label'] }}</option>
@endforeach
</select>
</div>
<div class="mt-3 text-sm text-slate-700">
@if($contoImport)
<div class="font-medium">{{ $contoImport['label'] }}</div>
@else
<div>Nessun conto selezionato.</div>
@endif
</div>
<div class="mt-3 flex flex-wrap gap-2">
<x-filament::button size="sm" color="primary" type="button" wire:click="mountAction('importa_estratto_unificato')">
Importa estratto
</x-filament::button>
<x-filament::button size="sm" color="gray" type="button" wire:click="mountAction('registra_saldo_estratto')">
Registra saldo
</x-filament::button>
</div>
</div>
</div>
</x-filament::section>
<x-filament::tabs>
<x-filament::tabs.item :active="$this->hubTab === 'conti'" wire:click="goToHubTab('conti')">Conti</x-filament::tabs.item>
<x-filament::tabs.item :active="$this->hubTab === 'saldi'" wire:click="goToHubTab('saldi')">Saldi e estratti</x-filament::tabs.item>
<x-filament::tabs.item :active="$this->hubTab === 'movimenti'" wire:click="goToHubTab('movimenti')">Movimenti</x-filament::tabs.item>
<x-filament::tabs.item :active="$this->hubTab === 'riconciliazione'" wire:click="goToHubTab('riconciliazione')">Riconciliazione</x-filament::tabs.item>
</x-filament::tabs>
@if($this->hubTab === 'conti')
<x-filament::section>
<x-slot name="heading">Conti dello stabile</x-slot>
<x-slot name="description">Vista generale equivalente a “Casse e banche”, ma dentro l'hub operativo del conto corrente.</x-slot>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
@forelse($contiRows as $row)
<div @class([
'rounded-xl border p-4',
'border-primary-300 bg-primary-50/40' => $row['selected'],
'bg-white' => ! $row['selected'],
])>
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-900">{{ $row['label'] }}</div>
<div class="mt-1 text-xs text-slate-600">
@if($row['snapshot_data'])
Ultimo saldo ufficiale: {{ $row['snapshot_data'] }}
@if($row['snapshot_tipo'])
· {{ $row['snapshot_tipo'] }}
@endif
@else
Nessun saldo ufficiale registrato.
@endif
</div>
</div>
@if($row['selected'])
<span class="rounded-full bg-primary-600 px-2.5 py-1 text-[11px] font-semibold text-white">Attivo</span>
@endif
</div>
<div class="mt-4 grid grid-cols-2 gap-3 text-sm">
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Saldo attuale</div>
<div class="mt-1 text-lg font-semibold text-slate-900">€ {{ number_format((float) ($row['saldo_attuale'] ?? 0), 2, ',', '.') }}</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Movimenti importati</div>
<div class="mt-1 text-lg font-semibold text-slate-900">{{ $row['movimenti_count'] ?? 0 }}</div>
<div class="text-xs text-slate-500">Ultima data: {{ $row['ultima_movimentazione'] ?? '' }}</div>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-2">
<x-filament::button size="sm" color="gray" wire:click="selectContoAndTab({{ $row['id'] }}, 'saldi')">Saldi / estratti</x-filament::button>
<x-filament::button size="sm" color="primary" wire:click="selectContoAndTab({{ $row['id'] }}, 'movimenti')">Movimenti</x-filament::button>
<x-filament::button size="sm" color="success" wire:click="selectContoAndTab({{ $row['id'] }}, 'riconciliazione')">Riconciliazione</x-filament::button>
</div>
</div>
@empty
<div class="rounded-lg border border-dashed bg-white p-4 text-sm text-slate-500 lg:col-span-2">Nessun conto configurato per lo stabile attivo.</div>
@endforelse
</div>
</x-filament::section>
@elseif($this->hubTab === 'saldi')
<x-filament::section>
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="text-lg font-semibold text-slate-900">Archivio saldi ufficiali ed estratti conto</div>
<div class="mt-1 text-sm text-slate-600">Vista annuale del conto selezionato, separata dal mastrino dei movimenti.</div>
@if($contoImport)
<div class="mt-2 text-xs font-medium text-slate-700">Conto: {{ $contoImport['label'] }}</div>
@endif
</div>
<x-filament::button type="button" color="primary" wire:click="mountAction('registra_saldo_estratto')">
Nuovo saldo / estratto
</x-filament::button>
</div>
<div class="mt-4 space-y-6">
@forelse($saldoArchivio as $yearBlock)
<div class="rounded-xl border bg-white p-4">
<div class="flex items-center justify-between gap-3">
<div class="text-base font-semibold text-slate-900">Anno {{ $yearBlock['year'] }}</div>
<div class="text-xs text-slate-500">{{ count($yearBlock['rows']) }} estratti / saldi</div>
</div>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-left text-slate-500">
<tr>
<th class="py-2 pr-4">Data saldo</th>
<th class="py-2 pr-4">Periodo</th>
<th class="py-2 pr-4">Tipo</th>
<th class="py-2 pr-4 text-right">Saldo</th>
<th class="py-2 pr-4">Documento</th>
<th class="py-2 pr-4">Note</th>
<th class="py-2 pr-0 text-right">Azioni</th>
</tr>
</thead>
<tbody class="divide-y">
@foreach($yearBlock['rows'] as $row)
<tr>
<td class="py-3 pr-4 font-medium text-slate-900">{{ $row['data'] }}</td>
<td class="py-3 pr-4">
<div>{{ $row['periodo_breve'] }}</div>
<div class="text-xs text-slate-500">{{ $row['periodo'] }}</div>
</td>
<td class="py-3 pr-4">{{ $row['tipo'] }}</td>
<td class="py-3 pr-4 text-right font-semibold">€ {{ number_format((float) $row['saldo'], 2, ',', '.') }}</td>
<td class="py-3 pr-4">
@if($row['documento_url'] && $row['documento_label'])
<a href="{{ $row['documento_url'] }}" target="_blank" class="text-primary-600 hover:underline">{{ $row['documento_label'] }}</a>
@else
<span class="text-slate-400">—</span>
@endif
</td>
<td class="py-3 pr-4 text-slate-600">{{ $row['note'] !== '' ? $row['note'] : '' }}</td>
<td class="py-3 pr-0 text-right">
<x-filament::button size="sm" color="gray" wire:click="openSaldoPeriodo({{ $row['id'] }})">Apri movimenti</x-filament::button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@empty
<div class="rounded-lg border border-dashed bg-white p-4 text-sm text-slate-500">Nessun saldo ufficiale registrato per il conto selezionato.</div>
@endforelse
</div>
</x-filament::section>
@elseif($this->hubTab === 'riconciliazione')
<x-filament::section>
<x-slot name="heading">Riconciliazione guidata</x-slot>
<x-slot name="description">Pre-riconciliazione automatica con candidati. Loperatore verifica un movimento alla volta e usa il mastrino dove il match non è certo.</x-slot>
<div class="grid grid-cols-2 gap-3 xl:grid-cols-4">
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Totale movimenti</div>
<div class="mt-1 text-lg font-semibold text-slate-900">{{ $ricStats['totale'] ?? 0 }}</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Senza prima nota</div>
<div class="mt-1 text-lg font-semibold text-slate-900">{{ $ricStats['senza_prima_nota'] ?? 0 }}</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Già collegati</div>
<div class="mt-1 text-lg font-semibold text-slate-900">{{ $ricStats['collegati'] ?? 0 }}</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-slate-500">Da confermare</div>
<div class="mt-1 text-lg font-semibold text-slate-900">{{ $ricStats['da_confermare'] ?? 0 }}</div>
</div>
</div>
@if(! $contoImport)
<div class="mt-4 rounded-lg border border-dashed bg-white p-4 text-sm text-slate-500">Seleziona un conto per avviare la riconciliazione.</div>
@elseif(! $ricCurrent)
<div class="mt-4 rounded-lg border border-dashed bg-white p-4 text-sm text-slate-500">Nessun movimento in coda. Il conto sembra già lavorato oppure i movimenti sono tutti collegati.</div>
@else
<div class="mt-4 grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(340px,0.9fr)]">
<div class="rounded-xl border bg-slate-50 p-4">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-xs uppercase tracking-wide text-slate-500">Movimento in lavorazione</div>
<div class="mt-1 text-sm text-slate-600">{{ ($this->riconciliazioneOffset ?? 0) + 1 }} di {{ count($ricQueue) }}</div>
</div>
<div class="flex gap-2">
<x-filament::button size="sm" color="gray" wire:click="previousRiconciliazione">Precedente</x-filament::button>
<x-filament::button size="sm" color="gray" wire:click="nextRiconciliazione">Successivo</x-filament::button>
</div>
</div>
<div class="mt-4 rounded-lg border bg-white p-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-900">{{ $ricCurrent['data'] }}</div>
<div class="mt-1 text-xs text-slate-500">Stato: {{ $ricCurrent['stato'] }}</div>
</div>
<div class="text-right text-lg font-semibold {{ $ricCurrent['importo'] >= 0 ? 'text-emerald-700' : 'text-rose-700' }}">
€ {{ number_format((float) $ricCurrent['importo'], 2, ',', '.') }}
</div>
</div>
<div class="mt-3 text-sm text-slate-700">{{ $ricCurrent['descrizione'] }}</div>
<div class="mt-4 flex flex-wrap gap-2">
<x-filament::button size="sm" color="primary" wire:click="goToHubTab('movimenti')">Apri nel mastrino</x-filament::button>
@if($ricCurrent['has_registrazione'])
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-medium text-emerald-700">Ha già una prima nota</span>
@else
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs font-medium text-amber-700">Da collegare</span>
@endif
</div>
</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold text-slate-900">Candidati automatici</div>
<div class="mt-1 text-xs text-slate-600">
@if($ricCurrent['importo'] >= 0)
Match su incassi registrati per importo, data e testo della causale.
@else
Match su fatture fornitore per importo, data e denominazione del fornitore.
@endif
</div>
<div class="mt-4 space-y-3">
@forelse($ricCandidates as $candidate)
<div class="rounded-lg border bg-slate-50 p-3">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-xs uppercase tracking-wide text-slate-500">{{ $candidate['tipo'] }}</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ $candidate['titolo'] }}</div>
</div>
<div class="rounded-full bg-primary-100 px-2.5 py-1 text-xs font-semibold text-primary-700">{{ $candidate['score'] }}%</div>
</div>
<div class="mt-2 text-sm text-slate-700">{{ $candidate['dettaglio'] }}</div>
<div class="mt-2 flex flex-wrap gap-4 text-xs text-slate-500">
<span>Data: {{ $candidate['data'] }}</span>
<span>Importo: € {{ number_format((float) $candidate['importo'], 2, ',', '.') }}</span>
</div>
<div class="mt-2 text-xs text-slate-600">{{ $candidate['motivo'] }}</div>
</div>
@empty
<div class="rounded-lg border border-dashed bg-slate-50 p-4 text-sm text-slate-500">Nessun candidato convincente. In questo caso loperatore lavora dal mastrino e conferma manualmente il collegamento.</div>
@endforelse
</div>
</div>
</div>
@endif
</x-filament::section>
@else
<x-filament::section>
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold">Import unico estratti</div>
<div class="mt-1 text-xs text-gray-600">
Seleziona il conto o la cassa corretta e importa il file nel formato relativo. Il collegamento al conto resta esplicito, senza inferenze dal file.
</div>
@if($contoImport)
<div class="mt-2 text-xs text-gray-700">
Conto selezionato: <span class="font-medium">{{ $contoImport['label'] }}</span>
</div>
@endif
</div>
<x-filament::button type="button" color="primary" wire:click="mountAction('importa_estratto_unificato')">
Importa estratto
</x-filament::button>
</div>
</x-filament::section>
<x-filament::section>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm font-medium">
{{ $hdr['label'] }}
@if($hdr['saldo'] !== null && $hdr['data'])
· saldo al: {{ $hdr['data'] }} € {{ number_format((float) $hdr['saldo'], 2, ',', '.') }}
@endif
</div>
<div class="text-xs text-gray-600">Filtra per periodo/gestione dai filtri tabella.</div>
</div>
@if($this->movimentiFocusFrom || $this->movimentiFocusTo)
<div class="mt-4 flex flex-wrap items-center justify-between gap-3 rounded-lg border bg-amber-50 p-3 text-sm text-amber-900">
<div>
Filtro rapido attivo sul periodo
<span class="font-semibold">{{ $this->movimentiFocusFrom ?: '' }}</span>
<span class="font-semibold">{{ $this->movimentiFocusTo ?: '' }}</span>
</div>
<x-filament::button size="sm" color="gray" wire:click="clearMovimentiFocus">Rimuovi focus</x-filament::button>
</div>
@endif
<div class="mt-4 grid grid-cols-1 gap-3 lg:grid-cols-4">
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-gray-500">Periodo</div>
<div class="mt-2 flex flex-wrap gap-2">
<select wire:model="periodoTipo" class="rounded border-gray-300 text-xs">
<option value="mese">Mese</option>
<option value="trimestre">Trimestre</option>
</select>
<select wire:model="periodoValore" class="rounded border-gray-300 text-xs">
@foreach(($periodoOptions[$periodoTipo] ?? []) as $val => $label)
<option value="{{ $val }}">{{ $label }}</option>
@endforeach
</select>
<input type="number" wire:model="periodoAnno" class="w-24 rounded border-gray-300 text-xs" min="2000" max="2100" />
</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-gray-500">Totali periodo ({{ $periodo['label'] ?? '' }})</div>
<div class="mt-2 text-xs">
<div>Movimenti: <span class="font-semibold">{{ $periodo['count'] ?? 0 }}</span></div>
<div>Entrate: <span class="font-semibold">€ {{ number_format((float)($periodo['entrate'] ?? 0), 2, ',', '.') }}</span></div>
<div>Uscite: <span class="font-semibold">€ {{ number_format((float)($periodo['uscite'] ?? 0), 2, ',', '.') }}</span></div>
</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-gray-500">Periodo rate (senza anno)</div>
<div class="mt-2 flex flex-wrap gap-2">
<select wire:model="periodoRate" class="rounded border-gray-300 text-xs">
<option value="">—</option>
@foreach($periodoRateOptions as $val => $label)
<option value="{{ $val }}">{{ $label }}</option>
@endforeach
</select>
</div>
<div class="mt-2 text-xs">
<div>Movimenti: <span class="font-semibold">{{ $periodoRate['count'] ?? 0 }}</span></div>
<div>Entrate: <span class="font-semibold">€ {{ number_format((float)($periodoRate['entrate'] ?? 0), 2, ',', '.') }}</span></div>
<div>Uscite: <span class="font-semibold">€ {{ number_format((float)($periodoRate['uscite'] ?? 0), 2, ',', '.') }}</span></div>
</div>
</div>
<div class="rounded-lg border bg-white p-3">
<div class="text-xs text-gray-500">Saldo periodo</div>
<div class="mt-3 text-lg font-semibold">€ {{ number_format((float)($periodo['saldo'] ?? 0), 2, ',', '.') }}</div>
</div>
</div>
<div class="mt-4 rounded-lg border bg-slate-50 p-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-sm font-semibold text-slate-900">Snapshot ufficiali / estratti conto</div>
<div class="text-xs text-slate-600">I saldi ufficiali ancorano il ricalcolo del conto anche quando il conto non ha IBAN.</div>
</div>
<x-filament::button type="button" size="sm" color="gray" wire:click="mountAction('registra_saldo_estratto')">
Registra saldo / estratto
</x-filament::button>
</div>
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
@forelse($snapshotCards as $card)
<div class="rounded-lg border bg-white p-3 text-sm">
<div class="flex items-start justify-between gap-2">
<div class="font-medium text-slate-900">{{ $card['tipo'] }}</div>
<div class="text-xs text-slate-500">{{ $card['data'] ?? '' }}</div>
</div>
<div class="mt-2 text-lg font-semibold text-slate-900">€ {{ number_format((float) ($card['saldo'] ?? 0), 2, ',', '.') }}</div>
<div class="mt-1 text-xs text-slate-600">{{ $card['periodo'] }}</div>
@if(!empty($card['documento_url']) && !empty($card['documento_label']))
<a href="{{ $card['documento_url'] }}" target="_blank" class="mt-2 inline-flex text-xs font-medium text-indigo-700 hover:underline">{{ $card['documento_label'] }}</a>
@endif
</div>
@empty
<div class="rounded-lg border border-dashed bg-white p-3 text-sm text-slate-500 md:col-span-2 xl:col-span-3">Nessuno snapshot ufficiale registrato per il conto selezionato.</div>
@endforelse
</div>
</div>
<div class="mt-4">
{{ $this->table }}
</div>
</x-filament::section>
@endif
@endif
</div>
</x-filament-panels::page>