hasAnyRole(['super-admin', 'admin'])) { return true; } return $user->can('contabilita.casse-banche'); } public function mount(): void { $this->mountInteractsWithTable(); } protected function getTableQuery(): Builder { $user = Auth::user(); if (! $user instanceof User) { return SaldoConto::query()->whereRaw('1 = 0'); } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return SaldoConto::query()->whereRaw('1 = 0'); } return SaldoConto::query() ->with('conto') ->where('stabile_id', $stabileId); } public function table(Table $table): Table { return $table ->striped() ->defaultSort('data_saldo', 'desc') ->columns([ TextColumn::make('conto_label') ->label('Conto') ->getStateUsing(function (SaldoConto $record): string { $c = $record->conto; if ($c instanceof DatiBancari) { $iban = is_string($c->iban) ? trim((string) $c->iban) : ''; return trim((string) ($c->denominazione_banca ?: 'Conto') . ($iban !== '' ? (' · ' . $iban) : '')); } $iban = is_string($record->iban) ? trim((string) $record->iban) : ''; return $iban !== '' ? $iban : '—'; }) ->searchable(query: function (Builder $query, string $search): Builder { $search = trim($search); if ($search === '') { return $query; } return $query->where(function (Builder $q) use ($search): void { $q->where('iban', 'like', '%' . $search . '%') ->orWhereHas('conto', fn(Builder $cq) => $cq->where('denominazione_banca', 'like', '%' . $search . '%')); }); }), TextColumn::make('data_saldo') ->label('Data') ->date('d/m/Y') ->sortable(), TextColumn::make('saldo') ->label('Saldo') ->alignEnd() ->formatStateUsing(fn($state) => '€ ' . number_format((float) $state, 2, ',', '.')), TextColumn::make('tipo_estratto') ->label('Tipo') ->getStateUsing(fn(SaldoConto $record): string => $this->formatTipoEstrattoLabel($record->tipo_estratto)) ->toggleable(), TextColumn::make('periodo_estratto') ->label('Periodo estratto') ->getStateUsing(fn(SaldoConto $record): string => $this->formatPeriodoEstrattoLabel($record->periodo_da, $record->periodo_a)) ->wrap() ->toggleable(), TextColumn::make('documento') ->label('Documento') ->getStateUsing(fn(SaldoConto $record): string => $record->documento?->nome_originale ?: '—') ->url(fn(SaldoConto $record): ?string => $record->documento?->url_view) ->openUrlInNewTab() ->toggleable(), TextColumn::make('note') ->label('Note') ->wrap() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('updated_at') ->label('Aggiornato') ->dateTime('d/m/Y H:i') ->toggleable(isToggledHiddenByDefault: true), ]) ->headerActions([ Action::make('create') ->label('Nuovo saldo') ->icon('heroicon-o-plus') ->form([ Select::make('conto_id') ->label('Conto') ->native(false) ->searchable() ->options(fn(): array => $this->getContiOptions()) ->required(), DatePicker::make('data_saldo')->label('Data')->required(), DatePicker::make('periodo_da')->label('Periodo dal'), DatePicker::make('periodo_a')->label('Periodo al'), TextInput::make('saldo')->label('Saldo')->numeric()->required(), Select::make('tipo_estratto') ->label('Tipo riferimento') ->native(false) ->options($this->getTipoEstrattoOptions()) ->default('estratto_conto'), FileUpload::make('documento') ->label('Allega estratto') ->directory('tmp/banca') ->disk('local') ->acceptedFileTypes([ 'application/pdf', 'text/plain', 'text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.pdf', '.csv', '.txt', '.qif', '.xlsx', ]), Textarea::make('note')->label('Note (opzionale)')->rows(2)->maxLength(255), ]) ->action(function (array $data): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return; } $contoId = isset($data['conto_id']) && is_numeric($data['conto_id']) ? (int) $data['conto_id'] : 0; if ($contoId <= 0) { return; } $conto = DatiBancari::query() ->where('stabile_id', $stabileId) ->where('id', $contoId) ->first(['id', 'denominazione_banca', 'iban', 'numero_conto', 'legacy_cod_cassa']); $iban = $conto && is_string($conto->iban) ? trim((string) $conto->iban) : null; $iban = is_string($iban) && $iban !== '' ? $iban : null; $documento = $conto ? $this->storeSaldoDocumento($stabileId, $conto, $data['documento'] ?? null, $user, $data) : null; SaldoConto::query()->create([ 'stabile_id' => (int) $stabileId, 'conto_id' => $contoId, 'iban' => $iban, 'data_saldo' => $data['data_saldo'] ?? null, 'periodo_da' => $data['periodo_da'] ?? null, 'periodo_a' => $data['periodo_a'] ?? null, 'saldo' => isset($data['saldo']) ? (float) $data['saldo'] : 0.0, 'tipo_estratto' => $this->normalizeTipoEstrattoValue($data['tipo_estratto'] ?? null), 'note' => isset($data['note']) && is_string($data['note']) ? trim((string) $data['note']) : null, 'documento_stabile_id' => $documento?->id, 'created_by' => (int) ($user->id ?? 0) ?: null, 'updated_by' => (int) ($user->id ?? 0) ?: null, ]); }), ]) ->actions([ Action::make('edit') ->label('Modifica') ->icon('heroicon-o-pencil-square') ->form([ DatePicker::make('data_saldo')->label('Data')->required(), DatePicker::make('periodo_da')->label('Periodo dal'), DatePicker::make('periodo_a')->label('Periodo al'), TextInput::make('saldo')->label('Saldo')->numeric()->required(), Select::make('tipo_estratto') ->label('Tipo riferimento') ->native(false) ->options($this->getTipoEstrattoOptions()), FileUpload::make('documento') ->label('Sostituisci / aggiungi allegato') ->directory('tmp/banca') ->disk('local') ->acceptedFileTypes([ 'application/pdf', 'text/plain', 'text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.pdf', '.csv', '.txt', '.qif', '.xlsx', ]), Textarea::make('note')->label('Note (opzionale)')->rows(2)->maxLength(255), ]) ->fillForm(fn(SaldoConto $record): array => [ 'data_saldo' => $record->data_saldo, 'periodo_da' => $record->periodo_da, 'periodo_a' => $record->periodo_a, 'saldo' => (float) $record->saldo, 'tipo_estratto' => $record->tipo_estratto, 'note' => $record->note, ]) ->action(function (SaldoConto $record, array $data): void { $user = Auth::user(); $documento = $record->conto ? $this->storeSaldoDocumento((int) $record->stabile_id, $record->conto, $data['documento'] ?? null, $user instanceof User ? $user : null, $data) : null; $record->fill([ 'data_saldo' => $data['data_saldo'] ?? $record->data_saldo, 'periodo_da' => $data['periodo_da'] ?? null, 'periodo_a' => $data['periodo_a'] ?? null, 'saldo' => isset($data['saldo']) ? (float) $data['saldo'] : (float) $record->saldo, 'tipo_estratto' => $this->normalizeTipoEstrattoValue($data['tipo_estratto'] ?? null), 'note' => isset($data['note']) && is_string($data['note']) ? trim((string) $data['note']) : null, 'documento_stabile_id' => $documento?->id ?: $record->documento_stabile_id, 'updated_by' => $user instanceof User ? ((int) $user->id ?: null) : null, ]); $record->save(); }), Action::make('delete') ->label('Elimina') ->icon('heroicon-o-trash') ->color('danger') ->requiresConfirmation() ->action(fn(SaldoConto $record) => $record->delete()), ]); } /** @return array */ protected function getContiOptions(): array { $user = Auth::user(); if (! $user instanceof User) { return []; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return []; } if (! DB::getSchemaBuilder()->hasTable('dati_bancari')) { return []; } return DatiBancari::query() ->where('stabile_id', $stabileId) ->orderBy('stato_conto') ->orderBy('denominazione_banca') ->orderBy('iban') ->orderBy('id') ->get(['id', 'denominazione_banca', 'iban']) ->mapWithKeys(function (DatiBancari $r): array { $iban = is_string($r->iban) ? trim((string) $r->iban) : ''; $label = trim((string) ($r->denominazione_banca ?: 'Conto') . ($iban !== '' ? (' · ' . $iban) : '')); return [(int) $r->id => $label]; }) ->all(); } protected function getTipoEstrattoOptions(): array { return [ 'estratto_conto' => 'Estratto conto', 'saldo_banca' => 'Saldo banca', 'riconciliazione' => 'Riconciliazione', 'altro' => 'Altro', ]; } protected function normalizeTipoEstrattoValue(mixed $value): ?string { $value = is_string($value) ? trim((string) $value) : ''; if ($value === '') { return null; } return array_key_exists($value, $this->getTipoEstrattoOptions()) ? $value : 'altro'; } protected function formatTipoEstrattoLabel(?string $value): string { $normalized = $this->normalizeTipoEstrattoValue($value); if (! $normalized) { return 'Saldo registrato'; } return $this->getTipoEstrattoOptions()[$normalized] ?? 'Saldo registrato'; } protected function formatPeriodoEstrattoLabel(mixed $from, mixed $to): string { $fromDate = $from ? \Illuminate\Support\Carbon::parse((string) $from) : null; $toDate = $to ? \Illuminate\Support\Carbon::parse((string) $to) : null; if ($fromDate && $toDate) { return $fromDate->format('d/m/Y') . ' - ' . $toDate->format('d/m/Y'); } if ($toDate) { return 'Fino al ' . $toDate->format('d/m/Y'); } if ($fromDate) { return 'Dal ' . $fromDate->format('d/m/Y'); } return 'Periodo non indicato'; } protected function storeSaldoDocumento(int $stabileId, DatiBancari $conto, mixed $tempPath, ?User $user, array $data): ?DocumentoStabile { $path = is_string($tempPath) ? trim((string) $tempPath) : ''; if ($path === '' || ! $user instanceof User) { return null; } $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $extension = $extension !== '' ? $extension : 'bin'; $filename = 'estratto-conto-' . $stabileId . '-' . (int) $conto->id . '-' . now()->format('YmdHis') . '.' . $extension; $publicPath = 'documenti/stabili/' . $stabileId . '/bancari/' . $filename; \Illuminate\Support\Facades\Storage::disk('public')->put($publicPath, \Illuminate\Support\Facades\Storage::disk('local')->get($path)); try { \Illuminate\Support\Facades\Storage::disk('local')->delete($path); } catch (\Throwable) { // ignore } return DocumentoStabile::query()->create([ 'stabile_id' => $stabileId, 'nome_file' => $filename, 'nome_originale' => $filename, 'percorso_file' => $publicPath, 'categoria' => 'bancari', 'tipo_mime' => \Illuminate\Support\Facades\Storage::disk('public')->mimeType($publicPath), 'dimensione' => \Illuminate\Support\Facades\Storage::disk('public')->size($publicPath), 'descrizione' => 'Estratto conto ' . trim((string) ($conto->denominazione_banca ?: 'Conto')) . ' · ' . $this->formatPeriodoEstrattoLabel($data['periodo_da'] ?? null, $data['periodo_a'] ?? null), 'caricato_da' => (int) $user->id, ]); } }