netgescon-day0/app/Filament/Pages/Contabilita/SaldiContiArchivio.php

450 lines
20 KiB
PHP

<?php
namespace App\Filament\Pages\Contabilita;
use App\Models\DatiBancari;
use App\Models\DocumentoStabile;
use App\Models\User;
use App\Modules\Contabilita\Models\SaldoConto;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Url;
use UnitEnum;
class SaldiContiArchivio extends Page implements HasTable
{
use InteractsWithTable;
#[Url(as: 'conto_id')]
public ?int $contoId = null;
protected static ?string $navigationLabel = 'Saldi conti';
protected static ?string $title = 'Saldi conti';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-scale';
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
protected static ?int $navigationSort = 12;
protected static ?string $slug = 'contabilita/saldi-conti';
protected string $view = 'filament.pages.gescon.table';
public static function canAccess(): bool
{
$user = Auth::user();
if (! $user instanceof User) {
return false;
}
if ($user->hasAnyRole(['super-admin', 'admin'])) {
return true;
}
return $user->can('contabilita.casse-banche');
}
public function mount(): void
{
$this->contoId = is_numeric(request()->query('conto_id')) ? (int) request()->query('conto_id') : null;
$this->contoId = $this->contoId && $this->contoId > 0 ? $this->contoId : null;
$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)
->when($this->contoId !== null, fn(Builder $query): Builder => $query->where('conto_id', $this->contoId));
}
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('is_partenza_contabile')
->label('Partenza')
->badge()
->getStateUsing(fn(SaldoConto $record): string => (bool) ($record->is_partenza_contabile ?? false) ? 'Si' : '—')
->color(fn(SaldoConto $record): string => (bool) ($record->is_partenza_contabile ?? false) ? 'info' : 'gray')
->toggleable(),
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'),
Toggle::make('is_partenza_contabile')
->label('Usa questo saldo come partenza contabile')
->default(false),
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;
$saldo = 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,
'is_partenza_contabile' => (bool) ($data['is_partenza_contabile'] ?? false),
'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,
]);
$this->syncContabilitaAnchorForSaldo($saldo);
}),
])
->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()),
Toggle::make('is_partenza_contabile')
->label('Usa questo saldo come partenza contabile'),
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,
'is_partenza_contabile' => (bool) ($record->is_partenza_contabile ?? false),
'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,
'is_partenza_contabile' => (bool) ($data['is_partenza_contabile'] ?? false),
'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();
$this->syncContabilitaAnchorForSaldo($record);
}),
Action::make('delete')
->label('Elimina')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->action(fn(SaldoConto $record) => $record->delete()),
]);
}
/** @return array<int,string> */
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,
]);
}
protected function syncContabilitaAnchorForSaldo(SaldoConto $saldo): void
{
if (! ((bool) ($saldo->is_partenza_contabile ?? false))) {
return;
}
SaldoConto::query()
->where('stabile_id', (int) $saldo->stabile_id)
->where('conto_id', (int) $saldo->conto_id)
->whereKeyNot($saldo->id)
->update(['is_partenza_contabile' => false]);
}
}