hasAnyRole(['super-admin', 'admin'])) { return true; } return $user->can('contabilita.casse-banche'); } public function mount(): void { $this->mountInteractsWithTable(); } public function getActiveStabile(): ?Stabile { $user = Auth::user(); if (! $user instanceof User) { return null; } return StabileContext::getActiveStabile($user); } protected function getHeaderActions(): array { return [ Action::make('importa_unicredit_wri') ->label('Importa estratto (Unicredit WRI)') ->icon('heroicon-o-arrow-up-tray') ->modalWidth('7xl') ->form([ Select::make('iban') ->label('Conto (IBAN)') ->native(false) ->searchable() ->options(fn(): array => $this->getIbanOptions()) ->helperText('Opzionale: se non selezionato, l\'import prova a rilevare il conto dai dati.'), Select::make('gestione_id') ->label('Gestione (opzionale)') ->native(false) ->searchable() ->options(fn(): array => $this->getGestioniOptions()) ->default(fn(): ?string => $this->resolveDefaultGestioneId()), FileUpload::make('file') ->label('File estratto') ->directory('tmp/banca') ->disk('local') ->required(), Toggle::make('replace') ->label('Sostituisci import nel periodo (consigliato se hai già importato con valori “slittati”)') ->helperText('Elimina SOLO i movimenti importati e NON riconciliati (registrazione vuota) per lo stesso conto e per l\'intervallo date del file, poi reimporta.') ->default(false), ]) ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { Notification::make()->title('Utente non valido')->danger()->send(); return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make()->title('Seleziona uno stabile')->danger()->send(); return; } $path = (string) ($data['file'] ?? ''); if ($path === '') { Notification::make()->title('Seleziona un file')->danger()->send(); return; } try { $content = Storage::disk('local')->get($path); $importer = new MovimentiBancaImporter(new UnicreditWriParser(), new IntesaCsvParser(), new ExcelMovimentiParser()); $res = $importer->importUnicreditWri( $content, $stabileId, basename($path), isset($data['iban']) && is_string($data['iban']) && trim($data['iban']) !== '' ? trim((string) $data['iban']) : null, isset($data['gestione_id']) && is_numeric($data['gestione_id']) ? (int) $data['gestione_id'] : null, isset($data['replace']) ? (bool) $data['replace'] : false, ); Notification::make() ->title('Import completato') ->body('Importati: ' . ($res['imported'] ?? 0) . ' · Duplicati: ' . ($res['duplicates'] ?? 0)) ->success() ->send(); } catch (\Throwable $e) { Notification::make()->title('Errore import')->body($e->getMessage())->danger()->send(); } finally { try { Storage::disk('local')->delete($path); } catch (\Throwable) { // ignore } } }), Action::make('inserisci_saldi') ->label('Inserisci saldi (periodo)') ->icon('heroicon-o-scale') ->modalWidth('7xl') ->form([ Select::make('iban') ->label('Conto (IBAN)') ->native(false) ->searchable() ->options(fn(): array => $this->getIbanOptions()) ->required(), Textarea::make('note') ->label('Note (opzionale)') ->rows(2) ->maxLength(255), Repeater::make('saldi') ->label('Saldi') ->defaultItems(1) ->reorderable(true) ->schema([ DatePicker::make('data_saldo') ->label('Data') ->required(), TextInput::make('saldo') ->label('Saldo') ->numeric() ->required(), ]) ->columns(2) ->required(), ]) ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { Notification::make()->title('Utente non valido')->danger()->send(); return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make()->title('Seleziona uno stabile')->danger()->send(); return; } $iban = isset($data['iban']) && is_string($data['iban']) ? trim((string) $data['iban']) : ''; if ($iban === '') { Notification::make()->title('Seleziona un IBAN')->danger()->send(); return; } $rows = $data['saldi'] ?? []; if (! is_array($rows) || $rows === []) { Notification::make()->title('Inserisci almeno un saldo')->danger()->send(); return; } $note = isset($data['note']) && is_string($data['note']) ? trim((string) $data['note']) : ''; $contoId = null; try { if (DB::getSchemaBuilder()->hasTable('dati_bancari') && DB::getSchemaBuilder()->hasColumn('contabilita_saldi_conti', 'conto_id')) { $contoId = DatiBancari::query() ->where('stabile_id', (int) $stabileId) ->where('iban', $iban) ->orderBy('id') ->value('id'); $contoId = is_numeric($contoId) ? (int) $contoId : null; } } catch (\Throwable) { $contoId = null; } $created = 0; DB::transaction(function () use ($rows, $stabileId, $contoId, $iban, $note, &$created, $user): void { foreach ($rows as $row) { $date = $row['data_saldo'] ?? null; $saldo = $row['saldo'] ?? null; if (! $date || ! is_numeric($saldo)) { continue; } SaldoConto::query()->create([ 'stabile_id' => (int) $stabileId, 'conto_id' => $contoId, 'iban' => $iban, 'data_saldo' => $date, 'saldo' => (float) $saldo, 'note' => $note !== '' ? $note : null, 'created_by' => (int) ($user->id ?? 0) ?: null, 'updated_by' => (int) ($user->id ?? 0) ?: null, ]); $created++; } }); Notification::make() ->title('Saldi salvati') ->body('Inseriti: ' . $created) ->success() ->send(); }), ]; } protected function getTableQuery(): Builder { $user = Auth::user(); if (! $user instanceof User) { return DatiBancari::query()->whereRaw('1 = 0'); } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return DatiBancari::query()->whereRaw('1 = 0'); } return DatiBancari::query() ->where('stabile_id', $stabileId) ->orderBy('stato_conto') ->orderBy('denominazione_banca') ->orderBy('iban') ->orderBy('id'); } public function table(Table $table): Table { return $table ->striped() ->filters([ Filter::make('periodo') ->label('Periodo') ->form([ DatePicker::make('from')->label('Dal'), DatePicker::make('to')->label('Al'), ]), Filter::make('gestioni') ->label('Gestioni') ->form([ Select::make('gestione_ids') ->label('Gestione') ->multiple() ->options(fn(): array => $this->getGestioniOptions()) ->searchable(), ]), ]) ->columns([ TextColumn::make('denominazione_banca') ->label('Banca') ->searchable() ->wrap(), TextColumn::make('tipo_conto') ->label('Tipo') ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('intestazione_conto') ->label('Intestazione') ->wrap() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('iban') ->label('IBAN') ->searchable() ->copyable(), TextColumn::make('saldo_calcolato') ->label('Saldo') ->alignEnd() ->getStateUsing(fn(DatiBancari $record): ?float => $this->resolveSaldoConto($record)) ->formatStateUsing(fn($state) => $state !== null ? ('€ ' . number_format((float) $state, 2, ',', '.')) : '—'), TextColumn::make('stato_conto') ->label('Stato') ->toggleable(isToggledHiddenByDefault: true), ]) ->actions([ Action::make('movimenti') ->label('Movimenti') ->icon('heroicon-o-arrow-right') ->url(function (DatiBancari $record): string { return CasseBancheMovimenti::getUrl(panel: 'admin-filament') . '?conto_id=' . (int) $record->id; }), ]); } public function getTotaleDisponibilita(): ?float { $user = Auth::user(); if (! $user instanceof User) { return null; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return null; } $periodo = $this->getTableFilterState('periodo'); $from = is_array($periodo) ? ($periodo['from'] ?? null) : null; $to = is_array($periodo) ? ($periodo['to'] ?? null) : null; $gestioni = $this->getTableFilterState('gestioni'); $gestioneIds = is_array($gestioni) ? ($gestioni['gestione_ids'] ?? null) : null; $gestioneIds = is_array($gestioneIds) ? array_values(array_filter(array_map('intval', $gestioneIds), fn(int $v) => $v > 0)) : []; $conti = DatiBancari::query() ->where('stabile_id', $stabileId) ->whereNotNull('iban') ->where('iban', '!=', '') ->get(['iban', 'saldo_iniziale']); if ($conti->isEmpty()) { return 0.0; } $ibans = $conti->pluck('iban')->map(fn($v) => trim((string) $v))->filter()->values()->all(); if ($ibans === []) { return 0.0; } $movQ = MovimentoBanca::query() ->where('stabile_id', $stabileId) ->whereIn('iban', $ibans); if ($from) { $movQ->whereDate('data', '>=', $from); } if ($to) { $movQ->whereDate('data', '<=', $to); } if ($gestioneIds !== []) { $movQ->whereIn('gestione_id', $gestioneIds); } $sums = $movQ ->groupBy('iban') ->selectRaw('iban, COALESCE(SUM(importo), 0) AS saldo_movimenti') ->pluck('saldo_movimenti', 'iban') ->map(fn($v) => (float) $v) ->all(); $total = 0.0; foreach ($conti as $c) { $iban = trim((string) ($c->iban ?? '')); if ($iban === '') { continue; } $saldoIniziale = is_numeric($c->saldo_iniziale) ? (float) $c->saldo_iniziale : 0.0; if ($from) { $alt = $this->resolveSaldoIniziale($stabileId, $iban, (string) $from); if ($alt !== null) { $saldoIniziale = $alt; } } $total += $saldoIniziale + ($sums[$iban] ?? 0.0); } return $total; } protected function resolveSaldoConto(DatiBancari $record): ?float { $user = Auth::user(); if (! $user instanceof User) { return null; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return null; } $iban = is_string($record->iban) ? trim((string) $record->iban) : ''; $periodo = $this->getTableFilterState('periodo'); $from = is_array($periodo) ? ($periodo['from'] ?? null) : null; $to = is_array($periodo) ? ($periodo['to'] ?? null) : null; $gestioni = $this->getTableFilterState('gestioni'); $gestioneIds = is_array($gestioni) ? ($gestioni['gestione_ids'] ?? null) : null; $gestioneIds = is_array($gestioneIds) ? array_values(array_filter(array_map('intval', $gestioneIds), fn(int $v) => $v > 0)) : []; $saldoIniziale = is_numeric($record->saldo_iniziale) ? (float) $record->saldo_iniziale : 0.0; $q = MovimentoBanca::query() ->where('stabile_id', $stabileId) ->when($record->id, fn($q) => $q->where('conto_id', (int) $record->id)) ->when($iban !== '', fn($q) => $q->where('iban', $iban)); if ($from) { $q->whereDate('data', '>=', $from); } if ($to) { $q->whereDate('data', '<=', $to); } if ($gestioneIds !== []) { $q->whereIn('gestione_id', $gestioneIds); } // Base da saldi intermedi: se c'è un saldo <= from (se filtrato) o altrimenti ultimo saldo disponibile. $baseSaldo = null; $baseDate = null; if (DB::getSchemaBuilder()->hasTable('contabilita_saldi_conti')) { $qSaldo = SaldoConto::query() ->where('stabile_id', $stabileId) ->where('conto_id', (int) $record->id); if ($from) { $qSaldo->whereDate('data_saldo', '<=', (string) $from); } $row = $qSaldo->orderByDesc('data_saldo')->orderByDesc('id')->first(['data_saldo', 'saldo']); if ($row) { $baseSaldo = (float) $row->saldo; $baseDate = $row->data_saldo->toDateString(); } } if ($from && $baseSaldo !== null) { // Se filtrato da una data, la base è già il saldo al from (o più vicino precedente). $saldoIniziale = $baseSaldo; } if ($baseSaldo !== null && ! $from) { // Non filtrato: usa ultimo saldo inserito come base. $saldoIniziale = $baseSaldo; } if ($baseDate && ! $from) { $q->whereDate('data', '>', $baseDate); } $sum = (float) ($q->sum('importo') ?? 0.0); return $saldoIniziale + $sum; } protected function resolveSaldoIniziale(int $stabileId, string $iban, string $fromDate): ?float { if (! DB::getSchemaBuilder()->hasTable('contabilita_saldi_conti')) { return null; } $row = SaldoConto::query() ->where('stabile_id', $stabileId) ->where('iban', $iban) ->whereDate('data_saldo', '<=', $fromDate) ->orderByDesc('data_saldo') ->orderByDesc('id') ->first(); return $row ? (float) $row->saldo : null; } /** @return array */ protected function getIbanOptions(): array { $user = Auth::user(); if (! $user instanceof User) { return []; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return []; } return DatiBancari::query() ->where('stabile_id', $stabileId) ->whereNotNull('iban') ->where('iban', '!=', '') ->orderBy('denominazione_banca') ->orderBy('iban') ->limit(500) ->get(['iban', 'denominazione_banca']) ->mapWithKeys(fn(DatiBancari $r) => [ trim((string) $r->iban) => trim((string) ($r->denominazione_banca ?: 'Conto') . ' · ' . (string) $r->iban), ]) ->all(); } /** @return array */ protected function getGestioniOptions(): array { $user = Auth::user(); if (! $user instanceof User) { return []; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return []; } if (! DB::getSchemaBuilder()->hasTable('gestioni_contabili')) { return []; } return GestioneContabile::query() ->where('stabile_id', $stabileId) ->orderByDesc('anno_gestione') ->orderBy('tipo_gestione') ->orderByDesc('id') ->get(['id', 'denominazione', 'protocollo_prefix', 'stato']) ->mapWithKeys(fn(GestioneContabile $g) => [ (string) $g->id => trim(($g->protocollo_prefix ? $g->protocollo_prefix . ' · ' : '') . $g->denominazione) . ($g->stato !== 'aperta' ? (' (' . $g->stato . ')') : ''), ]) ->all(); } protected function resolveDefaultGestioneId(): ?string { $user = Auth::user(); if (! $user instanceof User) { return null; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return null; } if (! DB::getSchemaBuilder()->hasTable('gestioni_contabili')) { return null; } $anno = AnnoGestioneContext::resolveActiveAnno(); $tipo = GestioneContext::resolveActiveGestione(); $match = GestioneContabile::query() ->where('stabile_id', $stabileId) ->where('anno_gestione', $anno) ->where('tipo_gestione', $tipo) ->where('stato', 'aperta') ->orderByDesc('gestione_attiva') ->orderByDesc('id') ->first(); if (! $match) { $match = GestioneContabile::query() ->where('stabile_id', $stabileId) ->where('stato', 'aperta') ->orderByDesc('gestione_attiva') ->orderByDesc('id') ->first(); } return $match ? (string) $match->id : null; } }