984 lines
40 KiB
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();
|
|
}),
|
|
]);
|
|
}
|
|
}
|