netgescon-day0/app/Livewire/Condomini/StabileDatiBancariTable.php

570 lines
23 KiB
PHP
Executable File

<?php
namespace App\Livewire\Condomini;
use App\Filament\Pages\Contabilita\CasseBancheMovimenti;
use App\Filament\Pages\Contabilita\SaldiContiArchivio;
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
use App\Models\DatiBancari;
use App\Models\RubricaUniversale;
use App\Models\Stabile;
use App\Models\User;
use App\Modules\Contabilita\Models\SaldoConto;
use App\Services\GesconImport\StabileEnrichmentService;
use App\Support\StabileContext;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Support\Contracts\TranslatableContentDriver;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Tables\TableComponent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class StabileDatiBancariTable extends TableComponent
{
public int $stabileId;
public ?int $editingContoId = null;
public bool $isCreatingConto = false;
public array $contoFormState = [];
public array $contattiBancaOptions = [];
public function mount(int $stabileId): void
{
$this->stabileId = $stabileId;
$this->mountInteractsWithTable();
$this->loadContattiBancaOptions();
}
protected function getTableQuery(): Builder
{
$user = Auth::user();
if (! $user instanceof User) {
return DatiBancari::query()->whereRaw('1 = 0');
}
$allowed = StabileContext::accessibleStabili($user)->pluck('id')->map(fn($v) => (int) $v)->all();
if (! in_array((int) $this->stabileId, $allowed, true)) {
return DatiBancari::query()->whereRaw('1 = 0');
}
return DatiBancari::query()
->with(['contatto'])
->where('stabile_id', (int) $this->stabileId)
->orderByDesc('is_nostro_conto')
->orderBy('denominazione_banca')
->orderByDesc('id');
}
public function table(Table $table): Table
{
return $table
->striped()
->columns([
TextColumn::make('denominazione_banca')
->label('Banca / Cassa')
->wrap()
->searchable(),
TextColumn::make('tipo_conto')
->label('Tipo')
->sortable(),
TextColumn::make('iban')
->label('IBAN')
->copyable()
->toggleable(),
IconColumn::make('is_nostro_conto')
->label('Nostro')
->boolean()
->toggleable(),
TextColumn::make('stato_conto')
->label('Stato')
->sortable(),
TextColumn::make('saldo_iniziale')
->label('Saldo iniz.')
->money('EUR')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('data_saldo_iniziale')
->label('Data saldo')
->date('d/m/Y')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('saldo_ufficiale')
->label('Saldo ufficiale')
->getStateUsing(fn(DatiBancari $record): ?float => $this->resolveLatestSaldoSnapshotValue($record))
->money('EUR')
->toggleable(),
TextColumn::make('ultimo_estratto')
->label('Ultimo estratto')
->getStateUsing(fn(DatiBancari $record): string => $this->resolveLatestSaldoSnapshotLabel($record))
->wrap()
->toggleable(),
TextColumn::make('contatto.nome_completo')
->label('Contatto')
->wrap()
->toggleable(isToggledHiddenByDefault: true),
])
->headerActions([
Action::make('importa_da_stabili_mdb')
->label('Importa da Stabili.mdb')
->icon('heroicon-o-arrow-down-tray')
->color('warning')
->requiresConfirmation()
->action(function (): void {
$user = Auth::user();
if (! $user instanceof User) {
Notification::make()->title('Utente non valido')->danger()->send();
return;
}
$stabile = Stabile::query()->with('amministratore')->find((int) $this->stabileId);
if (! $stabile) {
Notification::make()->title('Stabile non trovato')->danger()->send();
return;
}
$amm = $stabile->amministratore;
$adminCode = $amm ? (string) ($amm->codice_univoco ?: $amm->codice_amministratore ?: ''): '';
$adminCode = trim($adminCode);
if ($adminCode === '') {
$adminCode = $amm ? ('ID' . $amm->id) : 'ID0';
}
$adminCode = preg_replace('/[^A-Za-z0-9_-]+/', '_', $adminCode) ?: $adminCode;
$adminCode = trim((string) $adminCode, '_');
if ($adminCode === '') {
$adminCode = $amm ? ('ID' . $amm->id) : 'ID0';
}
$basePath = '/mnt/gescon-archives/gescon/' . $adminCode;
$legacyCode = (string) ($stabile->codice_stabile ?? ($stabile->cod_stabile ?? ''));
$legacyCode = str_pad(trim($legacyCode), 4, '0', STR_PAD_LEFT);
try {
(new StabileEnrichmentService())->enrich($legacyCode, $basePath);
} catch (\Throwable $e) {
Notification::make()
->title('Import fallito')
->body($e->getMessage())
->danger()
->send();
return;
}
Notification::make()
->title('Import completato')
->body('Dati bancari aggiornati da Stabili.mdb')
->success()
->send();
}),
Action::make('nuovo_conto')
->label('Nuovo conto')
->icon('heroicon-o-plus')
->color('success')
->action(function (): void {
$this->isCreatingConto = true;
$this->editingContoId = null;
$this->contoFormState = [
'denominazione_banca' => '',
'tipo_conto' => 'corrente',
'stato_conto' => 'attivo',
'iban' => '',
'numero_conto' => '',
'abi' => '',
'cab' => '',
'cin' => '',
'bic_swift' => '',
'cuc' => '',
'sia' => '',
'intestazione_conto' => '',
'data_saldo_iniziale' => null,
'saldo_iniziale' => 0,
'valuta' => 'EUR',
'is_nostro_conto' => false,
'contatto_id' => null,
'note' => '',
];
}),
])
->actions([
Action::make('movimenti')
->label('Movimenti')
->icon('heroicon-o-receipt-percent')
->url(function (DatiBancari $record): string {
$base = CasseBancheMovimenti::getUrl(panel: 'admin-filament');
$contoId = (int) ($record->id ?? 0);
if ($contoId <= 0) {
return $base;
}
return $base . '?' . http_build_query([
'conto_id' => $contoId,
]);
})
->openUrlInNewTab(),
Action::make('saldi')
->label('Saldi ed estratti')
->icon('heroicon-o-scale')
->url(fn(DatiBancari $record): string => CasseBancheMovimenti::getUrl(panel: 'admin-filament', parameters: ['conto_id' => (int) $record->id, 'tab' => 'saldi']))
->openUrlInNewTab(),
Action::make('contatto')
->label('Contatto')
->icon('heroicon-o-identification')
->visible(fn(DatiBancari $record): bool => (int) ($record->contatto_id ?? 0) > 0)
->url(fn(DatiBancari $record): string => RubricaUniversaleScheda::getUrl([
'record' => (int) $record->contatto_id,
], panel: 'admin-filament')),
Action::make('modifica')
->label('Modifica')
->icon('heroicon-o-pencil-square')
->action(function (DatiBancari $record): void {
$this->editingContoId = $record->id;
$this->isCreatingConto = false;
$this->contoFormState = [
'denominazione_banca' => $record->denominazione_banca,
'tipo_conto' => $record->tipo_conto,
'stato_conto' => $record->stato_conto,
'iban' => $record->iban,
'numero_conto' => $record->numero_conto,
'abi' => $record->abi,
'cab' => $record->cab,
'cin' => $record->cin,
'bic_swift' => $record->bic_swift,
'cuc' => $record->cuc,
'sia' => $record->sia,
'intestazione_conto' => $record->intestazione_conto,
'data_saldo_iniziale' => $record->data_saldo_iniziale?->format('Y-m-d'),
'saldo_iniziale' => $record->saldo_iniziale,
'valuta' => $record->valuta,
'is_nostro_conto' => (bool) $record->is_nostro_conto,
'contatto_id' => $record->contatto_id,
'note' => $record->note,
];
}),
Action::make('elimina')
->label('Elimina')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->action(function (DatiBancari $record): void {
$record->delete();
Notification::make()
->title('Conto eliminato')
->success()
->send();
}),
]);
}
public function makeFilamentTranslatableContentDriver(): ?TranslatableContentDriver
{
return null;
}
/**
* @return array<int, \Filament\Forms\Components\Component>
*/
private function formSchema(): array
{
return [
TextInput::make('denominazione_banca')
->label('Denominazione banca/cassa')
->required()
->maxLength(255),
Select::make('tipo_conto')
->label('Tipo conto')
->options([
'corrente' => 'Corrente',
'deposito' => 'Deposito',
'risparmio' => 'Risparmio',
])
->default('corrente')
->required(),
Select::make('stato_conto')
->label('Stato')
->options([
'attivo' => 'Attivo',
'sospeso' => 'Sospeso',
'chiuso' => 'Chiuso',
])
->default('attivo')
->required(),
TextInput::make('iban')
->label('IBAN')
->maxLength(64),
TextInput::make('numero_conto')
->label('Numero conto')
->maxLength(64),
TextInput::make('abi')
->label('ABI')
->maxLength(16),
TextInput::make('cab')
->label('CAB')
->maxLength(16),
TextInput::make('cin')
->label('CIN')
->maxLength(8),
TextInput::make('bic_swift')
->label('BIC / SWIFT')
->maxLength(32),
TextInput::make('cuc')
->label('CUC')
->maxLength(32),
TextInput::make('sia')
->label('SIA')
->maxLength(32),
TextInput::make('intestazione_conto')
->label('Intestazione conto')
->maxLength(255),
DatePicker::make('data_saldo_iniziale')
->label('Data saldo iniziale'),
TextInput::make('saldo_iniziale')
->label('Saldo iniziale')
->numeric()
->default(0),
TextInput::make('valuta')
->label('Valuta')
->maxLength(8)
->default('EUR'),
Toggle::make('is_nostro_conto')
->label('Nostro conto')
->default(false),
Select::make('contatto_id')
->label('Contatto banca (rubrica)')
->searchable()
->options(function (): array {
$records = RubricaUniversale::query()
->categoria('banca')
->attivi()
->orderBy('ragione_sociale')
->orderBy('cognome')
->orderBy('nome')
->get(['id', 'ragione_sociale', 'nome', 'cognome']);
return $records
->mapWithKeys(function (RubricaUniversale $r): array {
$label = trim((string) ($r->nome_completo ?? ''));
if ($label === '') {
$label = trim((string) ($r->ragione_sociale ?? ''));
}
if ($label === '') {
$label = trim((string) (($r->cognome ?? '') . ' ' . ($r->nome ?? '')));
}
if ($label === '') {
$label = 'Contatto #' . (int) $r->id;
}
return [(int) $r->id => $label];
})
->all();
})
->placeholder('—'),
Textarea::make('note')
->label('Note')
->rows(3),
];
}
/**
* @param array<string,mixed> $data
* @return array<string,mixed>
*/
private function normalizePayload(array $data): array
{
return [
'denominazione_banca' => isset($data['denominazione_banca']) ? trim((string) $data['denominazione_banca']) : null,
'tipo_conto' => isset($data['tipo_conto']) ? (string) $data['tipo_conto'] : null,
'stato_conto' => isset($data['stato_conto']) ? (string) $data['stato_conto'] : null,
'iban' => isset($data['iban']) ? trim((string) $data['iban']) : null,
'numero_conto' => isset($data['numero_conto']) ? trim((string) $data['numero_conto']) : null,
'abi' => isset($data['abi']) ? trim((string) $data['abi']) : null,
'cab' => isset($data['cab']) ? trim((string) $data['cab']) : null,
'cin' => isset($data['cin']) ? trim((string) $data['cin']) : null,
'bic_swift' => isset($data['bic_swift']) ? trim((string) $data['bic_swift']) : null,
'cuc' => isset($data['cuc']) ? trim((string) $data['cuc']) : null,
'sia' => isset($data['sia']) ? trim((string) $data['sia']) : null,
'intestazione_conto' => isset($data['intestazione_conto']) ? trim((string) $data['intestazione_conto']) : null,
'data_saldo_iniziale' => $data['data_saldo_iniziale'] ?? null,
'saldo_iniziale' => $data['saldo_iniziale'] ?? null,
'valuta' => isset($data['valuta']) ? trim((string) $data['valuta']) : null,
'is_nostro_conto' => (bool) ($data['is_nostro_conto'] ?? false),
'contatto_id' => ! empty($data['contatto_id']) ? (int) $data['contatto_id'] : null,
'note' => isset($data['note']) ? trim((string) $data['note']) : null,
];
}
private function resolveLatestSaldoSnapshot(DatiBancari $record): ?SaldoConto
{
static $cache = [];
$cacheKey = (int) $record->id;
if (array_key_exists($cacheKey, $cache)) {
return $cache[$cacheKey];
}
return $cache[$cacheKey] = SaldoConto::query()
->with('documento')
->where('stabile_id', (int) $record->stabile_id)
->where('conto_id', (int) $record->id)
->orderByDesc('data_saldo')
->orderByDesc('id')
->first();
}
private function resolveLatestSaldoSnapshotValue(DatiBancari $record): ?float
{
$snapshot = $this->resolveLatestSaldoSnapshot($record);
return $snapshot ? (float) $snapshot->saldo : null;
}
private function resolveLatestSaldoSnapshotLabel(DatiBancari $record): string
{
$snapshot = $this->resolveLatestSaldoSnapshot($record);
if (! $snapshot) {
return 'Nessun estratto registrato';
}
$parts = [];
if ($snapshot->tipo_estratto) {
$parts[] = match ($snapshot->tipo_estratto) {
'estratto_conto' => 'Estratto conto',
'saldo_banca' => 'Saldo banca',
'riconciliazione' => 'Riconciliazione',
default => 'Altro',
};
}
if ($snapshot->periodo_da || $snapshot->periodo_a) {
$from = $snapshot->periodo_da?->format('d/m/Y') ?: '...';
$to = $snapshot->periodo_a?->format('d/m/Y') ?: '...';
$parts[] = $from . ' - ' . $to;
} else {
$parts[] = $snapshot->data_saldo?->format('d/m/Y') ?: 'Data non indicata';
}
if ($snapshot->documento?->nome_originale) {
$parts[] = $snapshot->documento->nome_originale;
}
return implode(' · ', $parts);
}
public function loadContattiBancaOptions(): void
{
$records = RubricaUniversale::query()
->whereNull('deleted_at')
->orderBy('ragione_sociale')
->orderBy('cognome')
->orderBy('nome')
->get(['id', 'ragione_sociale', 'nome', 'cognome']);
$this->contattiBancaOptions = $records
->mapWithKeys(function (RubricaUniversale $r): array {
$label = trim((string) ($r->nome_completo ?? ''));
if ($label === '') {
$label = trim((string) ($r->ragione_sociale ?? ''));
}
if ($label === '') {
$label = trim((string) (($r->cognome ?? '') . ' ' . ($r->nome ?? '')));
}
if ($label === '') {
$label = 'Contatto #' . (int) $r->id;
}
return [(int) $r->id => $label];
})
->all();
}
public function saveContoInline(): void
{
$user = Auth::user();
$uid = $user instanceof User ? (int) $user->id : null;
$validatedData = validator($this->contoFormState, [
'denominazione_banca' => ['required', 'string', 'max:255'],
'tipo_conto' => ['required', 'string'],
'stato_conto' => ['required', 'string'],
'iban' => ['nullable', 'string', 'max:64'],
'numero_conto' => ['nullable', 'string', 'max:64'],
'abi' => ['nullable', 'string', 'max:16'],
'cab' => ['nullable', 'string', 'max:16'],
'cin' => ['nullable', 'string', 'max:8'],
'bic_swift' => ['nullable', 'string', 'max:32'],
'cuc' => ['nullable', 'string', 'max:32'],
'sia' => ['nullable', 'string', 'max:32'],
'intestazione_conto' => ['nullable', 'string', 'max:255'],
'data_saldo_iniziale' => ['nullable', 'date'],
'saldo_iniziale' => ['nullable', 'numeric'],
'valuta' => ['nullable', 'string', 'max:8'],
'is_nostro_conto' => ['boolean'],
'contatto_id' => ['nullable', 'integer'],
'note' => ['nullable', 'string'],
])->validate();
$payload = $this->normalizePayload($validatedData);
if ($this->isCreatingConto) {
$payload['stabile_id'] = (int) $this->stabileId;
if ($uid) {
$payload['creato_da'] = $uid;
$payload['modificato_da'] = $uid;
}
DatiBancari::create($payload);
Notification::make()->title('Conto creato con successo')->success()->send();
} else {
$record = DatiBancari::find($this->editingContoId);
if ($record) {
if ($uid) {
$payload['modificato_da'] = $uid;
}
$record->fill($payload);
$record->save();
Notification::make()->title('Conto aggiornato con successo')->success()->send();
}
}
$this->cancelInlineForm();
}
public function cancelInlineForm(): void
{
$this->isCreatingConto = false;
$this->editingContoId = null;
$this->contoFormState = [];
}
}