450 lines
20 KiB
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]);
|
|
}
|
|
}
|