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

984 lines
40 KiB
PHP

<?php
namespace App\Filament\Pages\Contabilita;
use App\Models\RegistroRitenuteAcconto;
use App\Models\User;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use UnitEnum;
class RegistroRitenuteAccontoArchivio extends Page implements HasTable
{
use InteractsWithTable;
public string $viewTab = 'ra';
public ?int $fiscalYear = null;
public string $f24SubTab = 'riepilogo';
public string $f24DetailFilterState = 'tutti';
public ?int $selectedF24FatturaId = null;
public string $cuSubTab = 'riepilogo';
public ?int $selectedCuFornitoreId = null;
protected array $tassoLegaleCache = [];
protected static ?string $navigationLabel = 'Registro RA';
protected static ?string $title = 'Registro ritenute d\'acconto';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-document-text';
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
protected static ?int $navigationSort = 36;
protected static ?string $slug = 'contabilita/registro-ra';
protected string $view = 'filament.pages.contabilita.registro-ra';
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.registro-ra');
}
public function mount(): void
{
$tab = (string) request()->query('tab', 'ra');
if (in_array($tab, ['ra', 'f24', 'cu'], true)) {
$this->viewTab = $tab;
}
$year = request()->query('anno');
$this->fiscalYear = is_numeric($year) ? (int) $year : (int) now()->format('Y');
$this->mountInteractsWithTable();
}
public function setViewTab(string $tab): void
{
if (in_array($tab, ['ra', 'f24', 'cu'], true)) {
$this->viewTab = $tab;
}
}
public function setFiscalYear($year): void
{
if (is_numeric($year)) {
$this->fiscalYear = (int) $year;
}
}
public function setF24SubTab(string $tab): void
{
if (in_array($tab, ['riepilogo', 'dettaglio'], true)) {
$this->f24SubTab = $tab;
}
}
public function setF24DetailFilterState(string $state): void
{
if (in_array($state, ['tutti', 'versato', 'da_versare', 'ritardo'], true)) {
$this->f24DetailFilterState = $state;
}
}
public function setSelectedF24Fattura(?int $fatturaId = null): void
{
$this->selectedF24FatturaId = $fatturaId && $fatturaId > 0 ? $fatturaId : null;
}
public function setCuSubTab(string $tab): void
{
if (in_array($tab, ['riepilogo', 'dettaglio'], true)) {
$this->cuSubTab = $tab;
}
}
public function setSelectedCuFornitore(?int $fornitoreId = null): void
{
$this->selectedCuFornitoreId = $fornitoreId && $fornitoreId > 0 ? $fornitoreId : null;
}
protected function getHeaderActions(): array
{
return [
Action::make('sync_operazioni_ra')
->label('Sincronizza date da Operazioni')
->icon('heroicon-o-arrow-path')
->action(function (): void {
$result = $this->syncRaDatesFromOperazioni();
Notification::make()
->title('Sincronizzazione completata')
->body("Fatture aggiornate: {$result['fatture_aggiornate']} · Registro RA aggiornato: {$result['registro_aggiornato']}")
->success()
->send();
}),
];
}
protected function getTableQuery(): Builder
{
$user = Auth::user();
if (! $user instanceof User) {
return RegistroRitenuteAcconto::query()->whereRaw('1 = 0');
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
return RegistroRitenuteAcconto::query()->whereRaw('1 = 0');
}
return RegistroRitenuteAcconto::query()
->with(['fornitore', 'gestione'])
->whereHas('gestione', fn(Builder $q) => $q->where('stabile_id', (int) $stabileId))
->when($this->fiscalYear, fn(Builder $q) => $q->where(function (Builder $sq): void {
$sq->whereYear('data_competenza', (int) $this->fiscalYear)
->orWhere('anno_dichiarazione', (int) $this->fiscalYear);
}))
->orderByDesc('data_competenza')
->orderByDesc('id');
}
public function getAvailableFiscalYearsProperty(): array
{
$stabileId = StabileContext::resolveActiveStabileId(Auth::user());
if (! $stabileId) {
return [];
}
$years = [];
if (Schema::hasTable('contabilita_fatture_fornitori')) {
$fattureYears = DB::table('contabilita_fatture_fornitori')
->where('stabile_id', (int) $stabileId)
->whereRaw('COALESCE(ritenuta_importo,0) <> 0')
->selectRaw('YEAR(data_documento) as y1, YEAR(data_pagamento) as y2, YEAR(data_versamento_ra) as y3')
->get();
foreach ($fattureYears as $y) {
foreach ([(int) ($y->y1 ?? 0), (int) ($y->y2 ?? 0), (int) ($y->y3 ?? 0)] as $year) {
if ($year > 0) {
$years[$year] = $year;
}
}
}
}
$registroYears = RegistroRitenuteAcconto::query()
->whereHas('gestione', fn(Builder $q) => $q->where('stabile_id', (int) $stabileId))
->pluck('anno_dichiarazione')
->filter(fn($v) => is_numeric($v) && (int) $v > 0)
->map(fn($v) => (int) $v)
->all();
foreach ($registroYears as $year) {
$years[$year] = $year;
}
if (empty($years)) {
$current = (int) now()->format('Y');
return [$current => (string) $current];
}
krsort($years);
return collect($years)->mapWithKeys(fn($y) => [$y => (string) $y])->all();
}
private function getRaDatasetRows(): array
{
$stabileId = StabileContext::resolveActiveStabileId(Auth::user());
if (! $stabileId || ! Schema::hasTable('contabilita_fatture_fornitori')) {
return [];
}
$query = DB::table('contabilita_fatture_fornitori as f')
->leftJoin('fornitori as fr', 'fr.id', '=', 'f.fornitore_id')
->leftJoin('contabilita_movimenti_banca as mb', 'mb.id', '=', 'f.movimento_versamento_ra_id')
->leftJoin('dati_bancari as db', 'db.id', '=', 'mb.conto_id')
->where('f.stabile_id', (int) $stabileId)
->whereRaw('COALESCE(f.ritenuta_importo,0) <> 0');
if ($this->fiscalYear) {
$year = (int) $this->fiscalYear;
$query->where(function ($q) use ($year): void {
$q->whereYear('f.data_documento', $year)
->orWhereYear('f.data_pagamento', $year)
->orWhereYear('f.data_versamento_ra', $year);
});
}
$rows = $query->get([
'f.id',
'f.fornitore_id',
'f.numero_documento',
'f.data_documento',
'f.data_pagamento',
'f.data_versamento_ra',
'f.ritenuta_importo',
'f.ritenuta_codice_tributo',
'f.movimento_versamento_ra_id',
'f.imponibile',
'f.totale',
'fr.ragione_sociale as fornitore_ragione_sociale',
'fr.nome as fornitore_nome',
'fr.cognome as fornitore_cognome',
'db.denominazione_banca as banca_nome',
'db.abi as banca_abi',
'db.cab as banca_cab',
'mb.data as banca_data',
'mb.descrizione as banca_descrizione',
'mb.descrizione_estesa as banca_descrizione_estesa',
]);
$fornitoreIds = $rows->pluck('fornitore_id')
->filter(fn($v) => is_numeric($v))
->map(fn($v) => (int) $v)
->unique()
->values()
->all();
$tributoByFornitore = $this->resolveTributoByFornitoreIdMap($fornitoreIds);
$registroMap = RegistroRitenuteAcconto::query()
->whereIn('fattura_id', $rows->pluck('id')->map(fn($v) => (int) $v)->all())
->get(['fattura_id', 'sanzioni', 'interessi', 'inclusa_cu', 'comunicazione_fornitore_inviata', 'data_comunicazione_fornitore', 'anno_dichiarazione'])
->keyBy('fattura_id');
return $rows->map(function ($row) use ($tributoByFornitore, $registroMap) {
$fornId = is_numeric($row->fornitore_id ?? null) ? (int) $row->fornitore_id : null;
$tributo = strtoupper(trim((string) ($row->ritenuta_codice_tributo ?? '')));
if ($tributo === '' && $fornId && ! empty($tributoByFornitore[$fornId])) {
$tributo = (string) $tributoByFornitore[$fornId];
}
if ($tributo === '') {
$tributo = '1040';
}
$fornitore = trim((string) ($row->fornitore_ragione_sociale ?? ''));
if ($fornitore === '') {
$fornitore = trim((string) (($row->fornitore_nome ?? '') . ' ' . ($row->fornitore_cognome ?? '')));
}
if ($fornitore === '') {
$fornitore = '—';
}
$registro = $registroMap[(int) $row->id] ?? null;
$sanzioni = abs((float) ($registro->sanzioni ?? 0));
$interessi = abs((float) ($registro->interessi ?? 0));
$ra = abs((float) ($row->ritenuta_importo ?? 0));
$dataPagamento = ! empty($row->data_pagamento) ? Carbon::parse($row->data_pagamento)->format('Y-m-d') : null;
$dataVersamento = ! empty($row->data_versamento_ra) ? Carbon::parse($row->data_versamento_ra)->format('Y-m-d') : null;
$scadenza = $dataPagamento ? Carbon::parse($dataPagamento)->startOfMonth()->addMonthNoOverflow()->day(16)->format('Y-m-d') : null;
$inRitardo = false;
if ($scadenza && $dataVersamento) {
$inRitardo = Carbon::parse($dataVersamento)->gt(Carbon::parse($scadenza));
}
$giorniRitardo = $this->computeGiorniRitardo($scadenza, $dataVersamento);
$sanzionePerc = $this->computeSanzionePercentuale($giorniRitardo);
$rateLegale = $this->resolveTassoLegalePercent($scadenza ?: $dataPagamento ?: $dataVersamento);
$sanzioneCalcolata = $this->computeSanzioneRavvedimento($ra, $giorniRitardo);
$interessiCalcolati = $this->computeInteressiRavvedimento($ra, $giorniRitardo, $rateLegale);
$sanzioniFinali = $sanzioni > 0 ? $sanzioni : $sanzioneCalcolata;
$interessiFinali = $interessi > 0 ? $interessi : $interessiCalcolati;
$fiscalYear = (int) (($registro->anno_dichiarazione ?? null) ?: ($dataVersamento ? Carbon::parse($dataVersamento)->format('Y') : ($dataPagamento ? Carbon::parse($dataPagamento)->format('Y') : 0)));
return [
'fattura_id' => (int) $row->id,
'fornitore_id' => $fornId,
'fornitore' => $fornitore,
'numero_documento' => (string) ($row->numero_documento ?? '—'),
'data_documento' => ! empty($row->data_documento) ? Carbon::parse($row->data_documento)->format('Y-m-d') : null,
'data_pagamento' => $dataPagamento,
'data_versamento' => $dataVersamento,
'scadenza_versamento' => $scadenza,
'tributo' => $tributo,
'importo_ra' => round($ra, 2),
'sanzioni' => round($sanzioniFinali, 2),
'interessi' => round($interessiFinali, 2),
'istituto_banca' => trim((string) ($row->banca_nome ?? '')) ?: '—',
'istituto_abi' => trim((string) ($row->banca_abi ?? '')),
'istituto_cab' => trim((string) ($row->banca_cab ?? '')),
'addebito_descrizione' => trim((string) ($row->banca_descrizione_estesa ?? $row->banca_descrizione ?? '')),
'banca_data' => ! empty($row->banca_data) ? Carbon::parse($row->banca_data)->format('Y-m-d') : null,
'versata' => $dataVersamento !== null,
'in_ritardo' => $inRitardo,
'giorni_ritardo' => $giorniRitardo,
'sanzione_percentuale' => $sanzionePerc,
'tasso_legale' => $rateLegale,
'tributo_sanzione' => '8906',
'tributo_interessi' => '1991',
'totale_f24' => round($ra + $sanzioniFinali + $interessiFinali, 2),
'fiscal_year' => $fiscalYear,
'inclusa_cu' => (bool) ($registro->inclusa_cu ?? false),
'comunicata_cu' => (bool) ($registro->comunicazione_fornitore_inviata ?? false),
'data_comunicazione_cu' => ! empty($registro?->data_comunicazione_fornitore) ? Carbon::parse($registro->data_comunicazione_fornitore)->format('Y-m-d') : null,
];
})->values()->all();
}
private function computeGiorniRitardo(?string $scadenza, ?string $dataVersamento): int
{
if (! $scadenza) {
return 0;
}
$scadenzaDate = Carbon::parse($scadenza)->startOfDay();
$targetDate = $dataVersamento ? Carbon::parse($dataVersamento)->startOfDay() : now()->startOfDay();
if ($targetDate->lte($scadenzaDate)) {
return 0;
}
return (int) $scadenzaDate->diffInDays($targetDate);
}
private function computeSanzionePercentuale(int $giorniRitardo): float
{
if ($giorniRitardo <= 0) {
return 0.0;
}
if ($giorniRitardo <= 14) {
return round($giorniRitardo * 0.1, 4);
}
if ($giorniRitardo <= 30) {
return 1.5;
}
if ($giorniRitardo <= 90) {
return 1.67;
}
return 3.75;
}
private function computeSanzioneRavvedimento(float $imposta, int $giorniRitardo): float
{
if ($imposta <= 0 || $giorniRitardo <= 0) {
return 0.0;
}
return round($imposta * $this->computeSanzionePercentuale($giorniRitardo) / 100, 2);
}
private function computeInteressiRavvedimento(float $imposta, int $giorniRitardo, float $tassoLegalePercent): float
{
if ($imposta <= 0 || $giorniRitardo <= 0 || $tassoLegalePercent <= 0) {
return 0.0;
}
return round(($imposta * $tassoLegalePercent * $giorniRitardo) / 36500, 2);
}
private function resolveTassoLegalePercent(?string $dateRef): float
{
$fallback = 1.6;
$year = $dateRef ? (int) Carbon::parse($dateRef)->format('Y') : (int) now()->format('Y');
if (array_key_exists($year, $this->tassoLegaleCache)) {
return (float) $this->tassoLegaleCache[$year];
}
if (! Schema::connection('gescon_import')->hasTable('ravv')) {
$this->tassoLegaleCache[$year] = $fallback;
return $fallback;
}
$columns = Schema::connection('gescon_import')->getColumnListing('ravv');
$rateColumn = collect(['tasso_legale', 'tasso', 'interesse', 'interessi'])
->first(fn(string $c) => in_array($c, $columns, true));
if (! $rateColumn) {
$this->tassoLegaleCache[$year] = $fallback;
return $fallback;
}
$yearColumn = collect(['anno', 'year', 'esercizio'])
->first(fn(string $c) => in_array($c, $columns, true));
$query = DB::connection('gescon_import')->table('ravv');
if ($yearColumn) {
$query->where($yearColumn, '<=', $year)->orderByDesc($yearColumn);
}
$rate = $query->value($rateColumn);
if (! is_numeric($rate)) {
$this->tassoLegaleCache[$year] = $fallback;
return $fallback;
}
$this->tassoLegaleCache[$year] = (float) $rate;
return (float) $this->tassoLegaleCache[$year];
}
public function getF24MonthlySummaryProperty(): array
{
$rows = $this->getRaDatasetRows();
$summary = [];
$totVersato = 0.0;
$totDaVersare = 0.0;
foreach ($rows as $row) {
$importo = abs((float) ($row['importo_ra'] ?? 0));
if ($importo <= 0) {
continue;
}
$tributo = (string) ($row['tributo'] ?? '1040');
$periodDate = $row['data_versamento'] ?? $row['scadenza_versamento'] ?? $row['data_pagamento'] ?? $row['data_documento'] ?? null;
if (! $periodDate) {
continue;
}
$mese = Carbon::parse($periodDate)->format('Y-m');
$key = $mese . '|' . $tributo;
if (! isset($summary[$key])) {
$summary[$key] = [
'mese' => $mese,
'tributo' => $tributo,
'versato' => 0.0,
'da_versare' => 0.0,
'righe' => 0,
'f24_compilati' => 0,
];
}
$summary[$key]['righe']++;
if (! empty($row['data_versamento'])) {
$summary[$key]['f24_compilati']++;
}
if (! empty($row['versata'])) {
$summary[$key]['versato'] += $importo;
$totVersato += $importo;
} else {
$summary[$key]['da_versare'] += $importo;
$totDaVersare += $importo;
}
}
$rowsOut = collect($summary)
->map(function (array $row) {
$row['versato'] = round((float) $row['versato'], 2);
$row['da_versare'] = round((float) $row['da_versare'], 2);
$row['delta'] = round($row['da_versare'] - $row['versato'], 2);
return $row;
})
->sortBy(['mese', 'tributo'])
->values()
->all();
return [
'rows' => $rowsOut,
'totale_versato' => round($totVersato, 2),
'totale_da_versare' => round($totDaVersare, 2),
];
}
public function getF24DetailRowsProperty(): array
{
$rows = collect($this->getRaDatasetRows())
->sortBy([
fn($a, $b) => strcmp((string) ($a['data_versamento'] ?? $a['scadenza_versamento'] ?? ''), (string) ($b['data_versamento'] ?? $b['scadenza_versamento'] ?? '')),
fn($a, $b) => strcmp((string) ($a['tributo'] ?? ''), (string) ($b['tributo'] ?? '')),
])
->values()
->all();
return $rows;
}
public function getFilteredF24DetailRowsProperty(): array
{
$rows = collect($this->f24DetailRows);
$rows = match ($this->f24DetailFilterState) {
'versato' => $rows->filter(fn(array $r) => ! empty($r['versata'])),
'da_versare' => $rows->filter(fn(array $r) => empty($r['versata'])),
'ritardo' => $rows->filter(fn(array $r) => ! empty($r['in_ritardo']) || ((int) ($r['giorni_ritardo'] ?? 0) > 0)),
default => $rows,
};
return $rows->values()->all();
}
public function getSelectedF24ModelProperty(): ?array
{
if (! $this->selectedF24FatturaId) {
return null;
}
return collect($this->f24DetailRows)
->first(fn(array $row) => (int) ($row['fattura_id'] ?? 0) === (int) $this->selectedF24FatturaId);
}
public function getCuSummaryProperty(): array
{
$rows = $this->getRaDatasetRows();
$summary = [];
$totaleRa = 0.0;
foreach ($rows as $row) {
$importo = abs((float) ($row['importo_ra'] ?? 0));
if ($importo <= 0) {
continue;
}
$anno = (int) ($row['fiscal_year'] ?? 0);
if ($anno <= 0) {
continue;
}
$fornitoreId = (int) ($row['fornitore_id'] ?? 0);
$fornitoreNome = (string) ($row['fornitore'] ?? 'Fornitore non associato');
$key = $anno . '|' . $fornitoreId;
if (! isset($summary[$key])) {
$summary[$key] = [
'anno' => $anno,
'fornitore_id' => $fornitoreId,
'fornitore' => $fornitoreNome !== '' ? $fornitoreNome : '—',
'totale_ra' => 0.0,
'totale_versata' => 0.0,
'totale_da_versare' => 0.0,
'righe' => 0,
'inclusa_cu' => false,
'comunicata' => false,
'ultima_comunicazione' => null,
];
}
$summary[$key]['totale_ra'] += $importo;
$summary[$key]['righe']++;
if (! empty($row['versata'])) {
$summary[$key]['totale_versata'] += $importo;
} else {
$summary[$key]['totale_da_versare'] += $importo;
}
$summary[$key]['inclusa_cu'] = $summary[$key]['inclusa_cu'] || (bool) ($row['inclusa_cu'] ?? false);
$summary[$key]['comunicata'] = $summary[$key]['comunicata'] || (bool) ($row['comunicata_cu'] ?? false);
if (! empty($row['data_comunicazione_cu'])) {
$dt = Carbon::parse((string) $row['data_comunicazione_cu'])->format('Y-m-d');
if (empty($summary[$key]['ultima_comunicazione']) || $dt > $summary[$key]['ultima_comunicazione']) {
$summary[$key]['ultima_comunicazione'] = $dt;
}
}
$totaleRa += $importo;
}
$rowsOut = collect($summary)
->map(function (array $row) {
$row['totale_ra'] = round((float) $row['totale_ra'], 2);
$row['totale_versata'] = round((float) $row['totale_versata'], 2);
$row['totale_da_versare'] = round((float) $row['totale_da_versare'], 2);
return $row;
})
->sortByDesc('anno')
->values()
->all();
return ['rows' => $rowsOut, 'totale_ra' => round($totaleRa, 2)];
}
public function getCuDetailRowsProperty(): array
{
return collect($this->getRaDatasetRows())
->sortBy([
fn($a, $b) => strcmp((string) ($a['fornitore'] ?? ''), (string) ($b['fornitore'] ?? '')),
fn($a, $b) => strcmp((string) ($a['data_pagamento'] ?? ''), (string) ($b['data_pagamento'] ?? '')),
])
->values()
->all();
}
public function getFilteredCuDetailRowsProperty(): array
{
$rows = collect($this->cuDetailRows);
if ($this->selectedCuFornitoreId) {
$rows = $rows->filter(fn(array $row) => (int) ($row['fornitore_id'] ?? 0) === (int) $this->selectedCuFornitoreId);
}
return $rows->values()->all();
}
private function resolveTributoByFornitoreIdMap(array $fornitoreIds): array
{
if (empty($fornitoreIds) || ! Schema::hasTable('fornitori') || ! Schema::hasTable('stg_fornitori_gescon')) {
return [];
}
$fornitoriRows = DB::table('fornitori')->whereIn('id', $fornitoreIds)->get(['id', 'old_id']);
$oldIds = $fornitoriRows->pluck('old_id')
->filter(fn($v) => $v !== null && trim((string) $v) !== '')
->map(fn($v) => trim((string) $v))
->unique()
->values()
->all();
if (empty($oldIds)) {
return [];
}
$stgRows = DB::table('stg_fornitori_gescon')
->whereIn('cod_forn', $oldIds)
->get(['cod_forn', 'raw']);
$tributoByCod = [];
foreach ($stgRows as $stg) {
$cod = trim((string) ($stg->cod_forn ?? ''));
if ($cod === '') {
continue;
}
$raw = json_decode((string) ($stg->raw ?? ''), true);
if (! is_array($raw)) {
continue;
}
$trib = strtoupper(trim((string) ($raw['trib_1019_1020'] ?? '')));
if ($trib !== '') {
$tributoByCod[$cod] = $trib;
$noZero = ltrim($cod, '0');
if ($noZero !== '') {
$tributoByCod[$noZero] = $trib;
}
}
}
$out = [];
foreach ($fornitoriRows as $forn) {
$old = trim((string) ($forn->old_id ?? ''));
if ($old === '') {
continue;
}
$oldNoZero = ltrim($old, '0');
$trib = $tributoByCod[$old] ?? ($oldNoZero !== '' ? ($tributoByCod[$oldNoZero] ?? null) : null);
if ($trib) {
$out[(int) $forn->id] = $trib;
}
}
return $out;
}
private function syncRaDatesFromOperazioni(): array
{
$user = Auth::user();
if (! $user instanceof User) {
return ['fatture_aggiornate' => 0, 'registro_aggiornato' => 0];
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId || ! Schema::hasTable('contabilita_fatture_fornitori') || ! Schema::connection('gescon_import')->hasTable('operazioni')) {
return ['fatture_aggiornate' => 0, 'registro_aggiornato' => 0];
}
$stabile = DB::table('stabili')->where('id', (int) $stabileId)->first(['codice_stabile', 'cod_stabile', 'old_id']);
$legacyCodes = array_values(array_unique(array_filter([
trim((string) ($stabile->codice_stabile ?? '')),
trim((string) ($stabile->cod_stabile ?? '')),
trim((string) ($stabile->old_id ?? '')),
], fn($v) => $v !== '')));
$ops = DB::connection('gescon_import')->table('operazioni')
->when(! empty($legacyCodes) && Schema::connection('gescon_import')->hasColumn('operazioni', 'cod_stabile'), fn($q) => $q->whereIn('cod_stabile', $legacyCodes))
->get(['benef', 'rif_rda', 'dt_spe', 'cod_for', 'num_fat', 'netto_vers_rda', 'importo_euro_770', 'importo_euro', 'importo']);
$rdaRowsByCode = [];
$invoiceMap = [];
foreach ($ops as $op) {
$benef = trim((string) ($op->benef ?? ''));
$isRda = $benef !== '' && str_starts_with(strtoupper($benef), 'RDA:');
$rdaCode = trim((string) ($op->rif_rda ?? ''));
if ($rdaCode === '' && $isRda) {
$rdaCode = trim(substr($benef, 4));
}
if ($rdaCode === '') {
continue;
}
if ($isRda) {
$date = ! empty($op->dt_spe) ? Carbon::parse($op->dt_spe)->format('Y-m-d') : null;
$amount = abs((float) ($op->netto_vers_rda ?? $op->importo_euro_770 ?? $op->importo_euro ?? $op->importo ?? 0));
$existing = $rdaRowsByCode[$rdaCode] ?? null;
if (! $existing || ($date && $date >= (string) ($existing['data'] ?? ''))) {
$rdaRowsByCode[$rdaCode] = ['data' => $date, 'importo' => $amount];
}
continue;
}
$codFor = trim((string) ($op->cod_for ?? ''));
$num = strtoupper((string) preg_replace('/[^A-Z0-9]/', '', (string) ($op->num_fat ?? '')));
$date = ! empty($op->dt_spe) ? Carbon::parse($op->dt_spe)->format('Y-m-d') : null;
if ($codFor === '' || $num === '' || ! $date) {
continue;
}
$fornCodes = array_values(array_unique(array_filter([$codFor, ltrim($codFor, '0')], fn($v) => $v !== '')));
foreach ($fornCodes as $fc) {
$key = $fc . '|' . $num;
$existing = $invoiceMap[$key] ?? null;
if (! $existing || $date >= (string) ($existing['data_pagamento'] ?? '')) {
$invoiceMap[$key] = ['data_pagamento' => $date, 'rda_code' => $rdaCode];
}
}
}
$fornitori = DB::table('fornitori')->get(['id', 'old_id'])->keyBy('id');
$tributoByFornitore = $this->resolveTributoByFornitoreIdMap($fornitori->keys()->map(fn($v) => (int) $v)->all());
$fatture = DB::table('contabilita_fatture_fornitori')
->where('stabile_id', (int) $stabileId)
->get(['id', 'fornitore_id', 'numero_documento', 'data_pagamento', 'data_versamento_ra', 'ritenuta_importo', 'ritenuta_codice_tributo', 'imponibile', 'ritenuta_aliquota']);
$fattureAggiornate = 0;
$registroAggiornato = 0;
foreach ($fatture as $fattura) {
$forn = $fornitori[(int) ($fattura->fornitore_id ?? 0)] ?? null;
$old = trim((string) ($forn->old_id ?? ''));
$num = strtoupper((string) preg_replace('/[^A-Z0-9]/', '', (string) ($fattura->numero_documento ?? '')));
if ($old === '' || $num === '') {
continue;
}
$map = $invoiceMap[$old . '|' . $num] ?? (($old !== ltrim($old, '0')) ? ($invoiceMap[ltrim($old, '0') . '|' . $num] ?? null) : null);
if (! $map) {
continue;
}
$update = [];
if (empty($fattura->data_pagamento) && ! empty($map['data_pagamento'])) {
$update['data_pagamento'] = $map['data_pagamento'];
}
$rdaCode = trim((string) ($map['rda_code'] ?? ''));
if (empty($fattura->data_versamento_ra) && $rdaCode !== '' && ! empty($rdaRowsByCode[$rdaCode]['data'])) {
$update['data_versamento_ra'] = $rdaRowsByCode[$rdaCode]['data'];
}
if ((float) ($fattura->ritenuta_importo ?? 0) == 0.0 && $rdaCode !== '' && ! empty($rdaRowsByCode[$rdaCode]['importo'])) {
$update['ritenuta_importo'] = -abs((float) $rdaRowsByCode[$rdaCode]['importo']);
}
if (empty($fattura->ritenuta_codice_tributo) && ! empty($tributoByFornitore[(int) ($fattura->fornitore_id ?? 0)])) {
$update['ritenuta_codice_tributo'] = $tributoByFornitore[(int) $fattura->fornitore_id];
}
if (empty($update)) {
continue;
}
$update['updated_at'] = now();
DB::table('contabilita_fatture_fornitori')->where('id', (int) $fattura->id)->update($update);
$fattureAggiornate++;
if (Schema::hasTable('registro_ritenute_acconto')) {
$ra = abs((float) ($update['ritenuta_importo'] ?? $fattura->ritenuta_importo ?? 0));
if ($ra > 0) {
$registroUpdate = [
'data_pagamento_fattura' => $update['data_pagamento'] ?? $fattura->data_pagamento,
'data_versamento' => $update['data_versamento_ra'] ?? $fattura->data_versamento_ra,
'codice_tributo' => $update['ritenuta_codice_tributo'] ?? $fattura->ritenuta_codice_tributo ?? '1040',
'importo_ritenuta' => $ra,
'stato_versamento' => ! empty($update['data_versamento_ra'] ?? $fattura->data_versamento_ra) ? 'versata' : 'da_versare',
'updated_at' => now(),
];
$datePag = $registroUpdate['data_pagamento_fattura'];
if (! empty($datePag)) {
$registroUpdate['data_scadenza_versamento'] = Carbon::parse((string) $datePag)->startOfMonth()->addMonthNoOverflow()->day(16)->format('Y-m-d');
}
$updated = DB::table('registro_ritenute_acconto')
->where('fattura_id', (int) $fattura->id)
->update($registroUpdate);
if ($updated > 0) {
$registroAggiornato += $updated;
}
}
}
}
return ['fatture_aggiornate' => $fattureAggiornate, 'registro_aggiornato' => $registroAggiornato];
}
public function table(Table $table): Table
{
return $table
->striped()
->filters([
SelectFilter::make('stato_versamento')
->label('Stato')
->options([
'da_versare' => 'Da versare',
'versata' => 'Versata',
'compensata' => 'Compensata',
'annullata' => 'Annullata',
])
->query(fn(Builder $query, array $data): Builder =>
isset($data['value']) && $data['value'] !== ''
? $query->where('stato_versamento', $data['value'])
: $query
),
SelectFilter::make('mese_scadenza')
->label('Mese scadenza')
->options([
'1' => 'Gennaio',
'2' => 'Febbraio',
'3' => 'Marzo',
'4' => 'Aprile',
'5' => 'Maggio',
'6' => 'Giugno',
'7' => 'Luglio',
'8' => 'Agosto',
'9' => 'Settembre',
'10' => 'Ottobre',
'11' => 'Novembre',
'12' => 'Dicembre',
])
->query(fn(Builder $query, array $data): Builder =>
isset($data['value']) && $data['value'] !== ''
? $query->whereMonth('data_scadenza_versamento', (int) $data['value'])
: $query
),
])
->columns([
TextColumn::make('numero_progressivo')->label('N.')->sortable(),
TextColumn::make('data_competenza')->label('Competenza')->date('d/m/Y')->sortable(),
TextColumn::make('data_pagamento_fattura')->label('Pag. fattura')->date('d/m/Y')->toggleable(),
TextColumn::make('fornitore.ragione_sociale')->label('Fornitore')->wrap()->searchable(),
TextColumn::make('imponibile')->label('Imponibile')->money('EUR')->toggleable(),
TextColumn::make('aliquota_ritenuta')->label('%')->toggleable(),
TextColumn::make('importo_ritenuta')
->label('RA')
->money('EUR')
->sortable()
->summarize(
Sum::make()
->label('Totale')
->formatStateUsing(fn($state): string => '€ ' . number_format((float) $state, 2, ',', '.'))
),
TextColumn::make('data_scadenza_versamento')
->label('Scad.')
->date('d/m/Y')
->toggleable()
->getStateUsing(function (RegistroRitenuteAcconto $record): ?string {
return $record->data_pagamento_fattura ? $record->data_scadenza_versamento?->toDateString() : null;
}),
BadgeColumn::make('stato_versamento')->label('Stato')->colors([
'warning' => 'da_versare',
'success' => 'versata',
'primary' => 'compensata',
'danger' => 'annullata',
]),
])
->actions([
Action::make('imposta_pagamento_fattura')
->label('Imposta pagamento')
->icon('heroicon-o-calendar-days')
->visible(fn(RegistroRitenuteAcconto $record) => $record->stato_versamento === 'da_versare')
->form([
DatePicker::make('data_pagamento_fattura')
->label('Data pagamento fattura')
->required()
->default(fn(RegistroRitenuteAcconto $record) => $record->data_pagamento_fattura?->toDateString()),
])
->action(function (RegistroRitenuteAcconto $record, array $data): void {
$raw = $data['data_pagamento_fattura'] ?? null;
if (! is_string($raw) || trim($raw) === '') {
Notification::make()->title('Data non valida')->danger()->send();
return;
}
try {
DB::transaction(function () use ($record, $raw) {
$date = Carbon::parse($raw)->startOfDay();
$scadenza = $date->copy()->addMonthNoOverflow()->day(16);
$record->data_pagamento_fattura = $date->toDateString();
$record->data_scadenza_versamento = $scadenza->toDateString();
$record->save();
});
} catch (\Throwable $e) {
Notification::make()->title('Errore')->body($e->getMessage())->danger()->send();
return;
}
Notification::make()->title('Pagamento aggiornato')->success()->send();
}),
Action::make('segna_versata')
->label('Segna versata')
->icon('heroicon-o-check')
->visible(fn(RegistroRitenuteAcconto $record) => $record->stato_versamento === 'da_versare')
->form([
DatePicker::make('data_versamento')->label('Data versamento')->required()->default(now()->toDateString()),
TextInput::make('f24_riferimento')->label('Riferimento F24')->maxLength(100)->nullable(),
TextInput::make('importo_versato')->label('Importo versato')->nullable(),
])
->action(function (RegistroRitenuteAcconto $record, array $data): void {
try {
DB::transaction(function () use ($record, $data) {
$record->stato_versamento = 'versata';
$record->data_versamento = $data['data_versamento'] ?? null;
$record->f24_riferimento = $data['f24_riferimento'] ?? null;
if (isset($data['importo_versato']) && $data['importo_versato'] !== null && $data['importo_versato'] !== '') {
$record->importo_versato = (float) str_replace(',', '.', str_replace('.', '', (string) $data['importo_versato']));
}
$record->save();
});
} catch (\Throwable $e) {
Notification::make()->title('Errore')->body($e->getMessage())->danger()->send();
return;
}
Notification::make()->title('Ritenuta aggiornata')->success()->send();
}),
]);
}
}