netgescon-day0/resources/views/filament/pages/contabilita/fatture-elettroniche-p7m-ricevute.blade.php

331 lines
27 KiB
PHP
Executable File

<x-filament-panels::page>
<div class="mx-auto max-w-7xl space-y-4">
<x-filament.components.page-breadcrumbs
:breadcrumbs="[
['label' => 'Contabilità', 'url' => null],
['label' => 'Fatture ricevute (P7M)', 'url' => null],
]"
/>
<div x-data="{ tab: 'fe' }" class="space-y-4">
<x-filament::tabs label="Fatture ricevute">
<x-filament::tabs.item
alpine-active="tab === 'fe'"
x-on:click="tab = 'fe'"
icon="heroicon-o-document-text"
>
FE importate
</x-filament::tabs.item>
<x-filament::tabs.item
alpine-active="tab === 'scarichi'"
x-on:click="tab = 'scarichi'"
icon="heroicon-o-arrow-down-tray"
>
Scarichi da Cassetto Fiscale
</x-filament::tabs.item>
<x-filament::tabs.item
alpine-active="tab === 'ade'"
x-on:click="tab = 'ade'"
icon="heroicon-o-clipboard-document-list"
>
Lista AdE
</x-filament::tabs.item>
</x-filament::tabs>
<div x-show="tab === 'fe'">
<div class="mb-3 rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-900">
<div class="font-semibold">Stabile attivo per archivio FE</div>
<div class="mt-1">{{ $this->activeStabileLabel }}</div>
<div class="mt-1 text-xs text-sky-700">L'elenco mostra solo le fatture collegate allo stabile attivo selezionato nella topbar.</div>
</div>
{{ $this->table }}
<div class="mt-2 text-sm text-gray-500">
Totale fatture: <span class="font-semibold text-gray-700">{{ $this->getArchivioRigheVisteCount() }}</span>
</div>
</div>
<div x-show="tab === 'scarichi'" x-cloak wire:poll.5s.visible>
@php($anno = (int) ($this->cassettoAnno ?? now()->year))
@php($nowTs = now()->timestamp)
@php($quarterStatus = $this->getCassettoQuarterStatus($anno))
@php($yearSummary = $this->getCassettoYearSummary($anno))
@php($cassettoLogs = $this->getCassettoDownloadLogs())
@php($cassettoAvailable = in_array('cassetto_fiscale', (array) ($this->feModuleStatus['sources'] ?? []), true))
<x-filament::section>
<x-slot name="heading">Scarichi da Cassetto Fiscale</x-slot>
<div class="mb-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
<div class="font-semibold">Destinazione scarico corrente</div>
<div class="mt-1">{{ $this->activeStabileLabel }}</div>
<div class="mt-1 text-xs text-amber-700">I trimestri scaricati in questa tab vengono associati allo stabile attivo visibile in topbar al momento del click.</div>
</div>
@if(! $cassettoAvailable)
<div class="mb-3 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-900">
<div class="font-semibold">Cassetto Fiscale non abilitato per questo soggetto</div>
<div class="mt-1 text-xs text-red-700">Lo storico locale resta consultabile, ma i nuovi scarichi sono disattivati finche il servizio Cassetto Fiscale non viene abilitato sull'amministratore dello stabile attivo.</div>
</div>
@endif
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="text-sm text-gray-500">
Verifica trimestri gia scaricati e confronto tra riepilogo Cassetto, file ricevuti e archivio locale.
</div>
<div class="flex flex-wrap items-center gap-2">
<div class="text-xs text-gray-600">Vista</div>
<div class="inline-flex rounded-lg border border-gray-200 bg-white p-1 text-xs shadow-sm">
<button type="button" wire:click="setCassettoViewMode('cards')" class="rounded-md px-3 py-1.5 font-medium {{ $this->cassettoViewMode === 'cards' ? 'bg-amber-100 text-amber-900' : 'text-gray-600 hover:bg-gray-50' }}">Schede</button>
<button type="button" wire:click="setCassettoViewMode('table')" class="rounded-md px-3 py-1.5 font-medium {{ $this->cassettoViewMode === 'table' ? 'bg-amber-100 text-amber-900' : 'text-gray-600 hover:bg-gray-50' }}">Tabella desktop</button>
</div>
<div class="ml-2 text-xs text-gray-600">Anno</div>
<select wire:model.live="cassettoAnno" class="fi-input fi-input-wrp fi-select-input text-xs">
@php($y = (int) now()->year)
@for($i = 0; $i < 6; $i++)
@php($yy = $y - $i)
<option value="{{ $yy }}">{{ $yy }}</option>
@endfor
</select>
</div>
</div>
<div class="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Cassetto</div>
<div class="mt-1 text-xl font-semibold text-gray-900">{{ (int) $yearSummary['remote_total'] }}</div>
<div class="text-xs text-gray-500">da riepiloghi disponibili</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">File pacchetti</div>
<div class="mt-1 text-xl font-semibold text-gray-900">{{ (int) $yearSummary['files_total'] }}</div>
<div class="text-xs text-gray-500">XML/P7M trovati nei download</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Archivio FE</div>
<div class="mt-1 text-xl font-semibold text-gray-900">{{ (int) $yearSummary['local_total'] }}</div>
<div class="text-xs text-gray-500">fatture locali {{ $anno }}</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Lista AdE</div>
<div class="mt-1 text-xl font-semibold text-gray-900">{{ (int) $yearSummary['ade_total'] }}</div>
<div class="text-xs text-gray-500">righe di controllo</div>
</div>
<div class="rounded-lg border {{ ((int) $yearSummary['missing_estimate']) > 0 ? 'border-amber-300 bg-amber-50' : 'border-emerald-200 bg-emerald-50' }} px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide {{ ((int) $yearSummary['missing_estimate']) > 0 ? 'text-amber-700' : 'text-emerald-700' }}">Da verificare</div>
<div class="mt-1 text-xl font-semibold {{ ((int) $yearSummary['missing_estimate']) > 0 ? 'text-amber-950' : 'text-emerald-950' }}">{{ (int) $yearSummary['missing_estimate'] }}</div>
<div class="text-xs {{ ((int) $yearSummary['missing_estimate']) > 0 ? 'text-amber-700' : 'text-emerald-700' }}">differenza Cassetto vs archivio</div>
</div>
</div>
@if(!empty($quarterStatus))
@if($this->cassettoViewMode === 'cards')
<div class="mt-4 grid gap-3 lg:grid-cols-2">
@foreach($quarterStatus as $row)
@php($stato = (string) ($row['stato'] ?? ''))
@php($isComplete = $stato === 'COMPLETO')
@php($isVerify = $stato === 'VERIFICA')
@php($isDownloaded = (bool) ($row['download_complete'] ?? false))
@php($key = $row['quarter'] . '-' . $anno)
@php($clickTs = (int) ($this->quarterDownloads[$key] ?? 0))
@php($isBusy = $clickTs > 0 && ($nowTs - $clickTs) < 120)
@php($isRunning = $stato === 'IN CORSO')
@php($isBlocked = ! $cassettoAvailable)
@php($processed = (int) $row['imported'] + (int) $row['duplicates'] + (int) $row['errors'])
@php($filesTotal = (int) $row['files_total'])
@php($progress = $filesTotal > 0 ? min(100, (int) floor(($processed / max(1, $filesTotal)) * 100)) : ($isRunning ? 5 : 0))
<div class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<div class="text-lg font-semibold text-gray-900">{{ $row['quarter'] }} {{ $anno }}</div>
<span class="inline-flex rounded-md px-2 py-1 text-xs font-semibold {{ match ($stato) { 'COMPLETO' => 'bg-emerald-100 text-emerald-800', 'VERIFICA' => 'bg-amber-100 text-amber-800', 'ERRORE' => 'bg-red-100 text-red-800', 'IN CORSO' => 'bg-sky-100 text-sky-800', 'LOCALE' => 'bg-indigo-100 text-indigo-800', 'SALTATO' => 'bg-slate-100 text-slate-700', default => 'bg-gray-100 text-gray-700' } }}">
{{ $stato }}
</span>
</div>
<div class="mt-1 text-xs text-gray-500">
{{ \Carbon\Carbon::parse($row['dal'])->format('d-m-Y') }} - {{ \Carbon\Carbon::parse($row['al'])->format('d-m-Y') }}
@if(!empty($row['log_id']))
<span class="text-gray-400"> · log #{{ (int) $row['log_id'] }}</span>
@endif
</div>
</div>
<button
type="button"
class="netgescon-btn netgescon-btn-sm {{ ($isBusy || $isRunning || $isBlocked) ? 'netgescon-btn-outline-secondary opacity-50 cursor-not-allowed' : (($isComplete || ($isDownloaded && ! $isVerify)) ? 'netgescon-btn-outline-secondary' : 'netgescon-btn-primary') }}"
{{ ($isBusy || $isRunning || $isBlocked) ? 'disabled' : '' }}
wire:loading.attr="disabled"
wire:target="downloadCassettoQuarter"
wire:click="downloadCassettoQuarter('{{ $row['quarter'] }}','{{ $row['dal'] }}','{{ $row['al'] }}',{{ $anno }})"
>
<span wire:loading.remove wire:target="downloadCassettoQuarter">{{ $isComplete ? 'Aggiorna' : ($isVerify ? 'Ricontrolla' : 'Scarica') }}</span>
<span wire:loading wire:target="downloadCassettoQuarter">Avvio...</span>
</button>
</div>
<div class="mt-4 grid grid-cols-2 gap-2 sm:grid-cols-5">
<div class="rounded-md bg-gray-50 px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Cassetto</div>
<div class="text-base font-semibold text-gray-900">{{ ($row['remote_total'] ?? null) !== null ? (int) $row['remote_total'] : 'n/d' }}</div>
</div>
<div class="rounded-md bg-gray-50 px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">File</div>
<div class="text-base font-semibold text-gray-900">{{ $filesTotal }}</div>
</div>
<div class="rounded-md bg-gray-50 px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Archivio</div>
<div class="text-base font-semibold text-gray-900">{{ (int) $row['local_total'] }}</div>
</div>
<div class="rounded-md bg-gray-50 px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Lista AdE</div>
<div class="text-base font-semibold text-gray-900">{{ (int) $row['ade_total'] }}</div>
</div>
<div class="rounded-md {{ (($row['missing_estimate'] ?? null) !== null && (int) $row['missing_estimate'] > 0) ? 'bg-amber-50' : 'bg-emerald-50' }} px-3 py-2">
<div class="text-[11px] font-semibold uppercase tracking-wide {{ (($row['missing_estimate'] ?? null) !== null && (int) $row['missing_estimate'] > 0) ? 'text-amber-700' : 'text-emerald-700' }}">Da verificare</div>
<div class="text-base font-semibold {{ (($row['missing_estimate'] ?? null) !== null && (int) $row['missing_estimate'] > 0) ? 'text-amber-950' : 'text-emerald-950' }}">{{ ($row['missing_estimate'] ?? null) !== null ? (int) $row['missing_estimate'] : 'n/d' }}</div>
</div>
</div>
<div class="mt-4">
<div class="mb-1 flex justify-between text-xs text-gray-500">
<span>{{ $processed }} / {{ $filesTotal }} file lavorati</span>
<span>{{ $progress }}%</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-gray-100">
<div class="h-full rounded-full {{ $isRunning ? 'bg-sky-500' : ($isComplete ? 'bg-emerald-500' : 'bg-amber-500') }}" style="width: {{ $progress }}%"></div>
</div>
<div class="mt-2 text-xs text-gray-500">
{{ (int) $row['imported'] }} importate, {{ (int) $row['duplicates'] }} duplicate, {{ (int) $row['errors'] }} errori
@if(!empty($row['created_at']))
· aggiornato {{ \Carbon\Carbon::parse($row['created_at'])->format('d-m-Y H:i') }}
@endif
</div>
</div>
</div>
@endforeach
</div>
@else
<div class="mt-4 overflow-x-auto rounded-lg border border-gray-200 bg-white shadow-sm">
<table class="min-w-[78rem] w-full text-sm">
<thead class="bg-gray-50 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">
<tr>
<th class="px-3 py-3">Trimestre</th>
<th class="px-3 py-3">Periodo</th>
<th class="px-3 py-3">Stato</th>
<th class="px-3 py-3 text-right">Cassetto</th>
<th class="px-3 py-3 text-right">File</th>
<th class="px-3 py-3 text-right">Archivio FE</th>
<th class="px-3 py-3 text-right">Lista AdE</th>
<th class="px-3 py-3 text-right">Da verificare</th>
<th class="px-3 py-3">Avanzamento</th>
<th class="px-3 py-3 text-right">Import</th>
<th class="px-3 py-3 text-right">Dup.</th>
<th class="px-3 py-3 text-right">Err.</th>
<th class="px-3 py-3">Scarico</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach($quarterStatus as $row)
@php($stato = (string) ($row['stato'] ?? ''))
@php($isComplete = $stato === 'COMPLETO')
@php($isVerify = $stato === 'VERIFICA')
@php($isDownloaded = (bool) ($row['download_complete'] ?? false))
@php($key = $row['quarter'] . '-' . $anno)
@php($clickTs = (int) ($this->quarterDownloads[$key] ?? 0))
@php($isBusy = $clickTs > 0 && ($nowTs - $clickTs) < 120)
@php($isRunning = $stato === 'IN CORSO')
@php($isBlocked = ! $cassettoAvailable)
@php($processed = (int) $row['imported'] + (int) $row['duplicates'] + (int) $row['errors'])
@php($filesTotal = (int) $row['files_total'])
@php($progress = $filesTotal > 0 ? min(100, (int) floor(($processed / max(1, $filesTotal)) * 100)) : ($isRunning ? 5 : 0))
<tr class="align-middle">
<td class="px-3 py-3 font-semibold text-gray-900">{{ $row['quarter'] }} {{ $anno }}</td>
<td class="px-3 py-3 whitespace-nowrap text-gray-600">{{ \Carbon\Carbon::parse($row['dal'])->format('d-m-Y') }} - {{ \Carbon\Carbon::parse($row['al'])->format('d-m-Y') }}</td>
<td class="px-3 py-3 whitespace-nowrap">
<span class="inline-flex rounded-md px-2 py-1 text-xs font-semibold {{ match ($stato) { 'COMPLETO' => 'bg-emerald-100 text-emerald-800', 'VERIFICA' => 'bg-amber-100 text-amber-800', 'ERRORE' => 'bg-red-100 text-red-800', 'IN CORSO' => 'bg-sky-100 text-sky-800', 'LOCALE' => 'bg-indigo-100 text-indigo-800', 'SALTATO' => 'bg-slate-100 text-slate-700', default => 'bg-gray-100 text-gray-700' } }}">{{ $stato }}</span>
</td>
<td class="px-3 py-3 text-right">{{ ($row['remote_total'] ?? null) !== null ? (int) $row['remote_total'] : 'n/d' }}</td>
<td class="px-3 py-3 text-right">{{ $filesTotal }}</td>
<td class="px-3 py-3 text-right font-semibold text-gray-900">{{ (int) $row['local_total'] }}</td>
<td class="px-3 py-3 text-right">{{ (int) $row['ade_total'] }}</td>
<td class="px-3 py-3 text-right {{ (($row['missing_estimate'] ?? null) !== null && (int) $row['missing_estimate'] > 0) ? 'font-semibold text-amber-700' : 'text-emerald-700' }}">{{ ($row['missing_estimate'] ?? null) !== null ? (int) $row['missing_estimate'] : 'n/d' }}</td>
<td class="px-3 py-3 min-w-[12rem]">
<div class="mb-1 flex justify-between text-xs text-gray-500"><span>{{ $processed }}/{{ $filesTotal }}</span><span>{{ $progress }}%</span></div>
<div class="h-2 overflow-hidden rounded-full bg-gray-100"><div class="h-full rounded-full {{ $isRunning ? 'bg-sky-500' : ($isComplete ? 'bg-emerald-500' : 'bg-amber-500') }}" style="width: {{ $progress }}%"></div></div>
</td>
<td class="px-3 py-3 text-right">{{ (int) $row['imported'] }}</td>
<td class="px-3 py-3 text-right">{{ (int) $row['duplicates'] }}</td>
<td class="px-3 py-3 text-right">{{ (int) $row['errors'] }}</td>
<td class="px-3 py-3">
<button
type="button"
class="netgescon-btn netgescon-btn-sm {{ ($isBusy || $isRunning || $isBlocked) ? 'netgescon-btn-outline-secondary opacity-50 cursor-not-allowed' : (($isComplete || ($isDownloaded && ! $isVerify)) ? 'netgescon-btn-outline-secondary' : 'netgescon-btn-primary') }}"
{{ ($isBusy || $isRunning || $isBlocked) ? 'disabled' : '' }}
wire:loading.attr="disabled"
wire:target="downloadCassettoQuarter"
wire:click="downloadCassettoQuarter('{{ $row['quarter'] }}','{{ $row['dal'] }}','{{ $row['al'] }}',{{ $anno }})"
>
<span wire:loading.remove wire:target="downloadCassettoQuarter">{{ $isComplete ? 'Aggiorna' : ($isVerify ? 'Ricontrolla' : 'Scarica') }}</span>
<span wire:loading wire:target="downloadCassettoQuarter">Avvio...</span>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@endif
@if($cassettoLogs->isNotEmpty())
<div class="mt-6 space-y-2">
<div class="text-sm font-semibold text-gray-800">Ultimi scarichi</div>
@foreach($cassettoLogs as $log)
@php($logProcessed = (int) $log->imported + (int) $log->duplicates + (int) $log->errors)
@php($logFiles = (int) $log->files_total)
@php($logProgress = $logFiles > 0 ? min(100, (int) floor(($logProcessed / max(1, $logFiles)) * 100)) : 0)
<div class="rounded-lg border border-gray-200 bg-white px-4 py-3 text-sm shadow-sm">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="font-semibold text-gray-900">
{{ \Carbon\Carbon::parse($log->dal)->format('d-m-Y') }} - {{ \Carbon\Carbon::parse($log->al)->format('d-m-Y') }}
<span class="text-gray-400">#{{ (int) $log->id }}</span>
</div>
<div class="mt-1 text-xs text-gray-500">
{{ \Carbon\Carbon::parse($log->created_at)->format('d-m-Y H:i') }} · stato {{ strtoupper((string) $log->status) }} · metadati {{ $log->metadati ? 'si' : 'no' }}
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-right text-xs sm:grid-cols-5">
<div><span class="block text-gray-500">Cassetto</span><span class="font-semibold text-gray-900">{{ ($log->remote_total ?? null) !== null ? (int) $log->remote_total : 'n/d' }}</span></div>
<div><span class="block text-gray-500">File</span><span class="font-semibold text-gray-900">{{ $logFiles }}</span></div>
<div><span class="block text-gray-500">Archivio</span><span class="font-semibold text-gray-900">{{ (int) ($log->local_total ?? 0) }}</span></div>
<div><span class="block text-gray-500">Import</span><span class="font-semibold text-gray-900">{{ (int) $log->imported }}</span></div>
<div><span class="block text-gray-500">Errori</span><span class="font-semibold text-gray-900">{{ (int) $log->errors }}</span></div>
</div>
</div>
<div class="mt-3 h-2 overflow-hidden rounded-full bg-gray-100">
<div class="h-full rounded-full bg-sky-500" style="width: {{ $logProgress }}%"></div>
</div>
@if(!empty($log->message))
<div class="mt-2 break-words text-xs text-gray-500">{{ $log->message }}</div>
@endif
</div>
@endforeach
</div>
@endif
</x-filament::section>
</div>
<div x-show="tab === 'ade'" x-cloak>
<livewire:contabilita.stg-fatture-ade-table />
</div>
</div>
</div>
</x-filament-panels::page>