netgescon-day0/app/Filament/Pages/SuperAdmin/EstrattoreFatturePdf.php

337 lines
17 KiB
PHP

<?php
namespace App\Filament\Pages\SuperAdmin;
use App\Models\FatturaElettronica;
use App\Models\Documento;
use App\Models\User;
use App\Models\StabileServizio;
use App\Models\StabileServizioLettura;
use App\Services\Documenti\PdfTextExtractionService;
use App\Services\Consumi\AcquaPdfTextParser;
use App\Services\Consumi\GasLucePdfTextParser;
use App\Services\Consumi\ConsumiAcquaIngestionService;
use App\Services\Consumi\ConsumiRiscaldamentoIngestionService;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\DatePicker;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Filament\Actions\Action as TableAction;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use UnitEnum;
class EstrattoreFatturePdf extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationLabel = 'Estrattore PDF Utenze';
protected static ?string $title = 'Estrattore PDF Utenze';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-document-magnifying-glass';
protected static UnitEnum|string|null $navigationGroup = 'Super Admin';
protected static ?int $navigationSort = 27;
protected static ?string $slug = 'superadmin/estrattore-pdf';
protected string $view = 'filament.pages.gescon.table';
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User && $user->hasRole('super-admin');
}
public function mount(): void
{
$this->mountInteractsWithTable();
}
protected function getTableQuery(): Builder
{
return FatturaElettronica::query()
->with(['stabile', 'fornitore'])
->whereNotNull('allegato_pdf_path')
->where('allegato_pdf_path', '!=', '');
}
public function table(Table $table): Table
{
return $table
->striped()
->defaultSort('data_fattura', 'desc')
->columns([
TextColumn::make('stabile.denominazione')
->label('Stabile')
->searchable()
->sortable(),
TextColumn::make('fornitore_denominazione')
->label('Fornitore')
->searchable()
->sortable()
->description(fn (FatturaElettronica $record): string => "P.IVA: " . $record->fornitore_piva),
TextColumn::make('numero_fattura')
->label('Numero')
->searchable()
->sortable(),
TextColumn::make('data_fattura')
->label('Data')
->date('d/m/Y')
->sortable(),
TextColumn::make('totale')
->label('Totale')
->money('EUR')
->sortable(),
IconColumn::make('has_ocr')
->label('OCR Ready')
->boolean()
->getStateUsing(function (FatturaElettronica $record): bool {
$doc = Documento::query()
->where('documentable_type', FatturaElettronica::class)
->where('documentable_id', $record->id)
->first();
return $doc && !empty($doc->contenuto_ocr);
}),
TextColumn::make('dati_utenza')
->label('Dati Utenza Estratti')
->html()
->getStateUsing(function (FatturaElettronica $record): string {
$pdr = $record->acqua_contatore_matricola ?: '-';
$cliente = $record->acqua_codice_cliente ?: '-';
$contratto = $record->acqua_codice_contratto ?: '-';
$consumo = $record->consumo_valore !== null ? number_format((float) $record->consumo_valore, 2, ',', '.') . ' ' . $record->consumo_unita : '-';
// Controlla se c'è un payload salvato in consumo_raw
$raw = is_string($record->consumo_raw) ? json_decode($record->consumo_raw, true) : null;
if ($raw && isset($raw['codici'])) {
$pdr = $raw['codici']['pdr'] ?? ($raw['codici']['pod'] ?? ($raw['codici']['utenza'] ?? $pdr));
$cliente = $raw['codici']['cliente'] ?? $cliente;
$contratto = $raw['codici']['contratto'] ?? $contratto;
}
return "<div class='text-xs space-y-0.5'>"
. "<div><strong>Matr/POD/PDR:</strong> <code class='bg-slate-100 px-1 rounded'>$pdr</code></div>"
. "<div><strong>Cod. Cliente:</strong> $cliente</div>"
. "<div><strong>Contratto:</strong> $contratto</div>"
. "<div><strong>Consumo:</strong> <span class='font-semibold text-cyan-700'>$consumo</span></div>"
. "</div>";
}),
])
->actions([
TableAction::make('esegui_ocr')
->label('Esegui OCR')
->icon('heroicon-o-cpu-chip')
->action(function (FatturaElettronica $record): void {
$doc = Documento::query()
->where('documentable_type', FatturaElettronica::class)
->where('documentable_id', $record->id)
->first();
if (! $doc) {
$doc = Documento::query()->create([
'documentable_type' => FatturaElettronica::class,
'documentable_id' => $record->id,
'nome_file' => $record->allegato_pdf_nome ?: 'allegato.pdf',
'path_file' => $record->allegato_pdf_path,
'mime_type' => $record->allegato_pdf_mime ?: 'application/pdf',
'stabile_id' => $record->stabile_id,
'nome' => 'Allegato PDF Fattura #' . $record->numero_fattura,
]);
}
try {
$res = app(PdfTextExtractionService::class)->extractAndStore($doc, true);
if (($res['status'] ?? null) === 'ok') {
Notification::make()
->title('OCR completato con successo')
->success()
->send();
} else {
Notification::make()
->title('Errore OCR')
->body($res['error'] ?? 'Impossibile estrarre il testo dal PDF')
->danger()
->send();
}
} catch (\Throwable $e) {
Notification::make()
->title('Errore OCR')
->body($e->getMessage())
->danger()
->send();
}
}),
TableAction::make('estrai_dati')
->label('Estrai Utenza')
->icon('heroicon-o-magnifying-glass-circle')
->color('info')
->form([
Select::make('tipo_utenza')
->label('Tipo Utenza / Parser')
->options([
'gas_luce' => 'Gas / Luce (Riscaldamento)',
'acqua' => 'Acqua (Idrico)',
])
->required()
->default(function (FatturaElettronica $record) {
// Auto-detect
$piva = $record->fornitore_piva;
$op = \App\Models\AreraOperatore::query()->where('partita_iva_normalizzata', $piva)->first();
if ($op) {
$settori = $op->settori->pluck('nome')->map('mb_strtolower')->all();
foreach ($settori as $s) {
if (str_contains($s, 'idric') || str_contains($s, 'acqua')) {
return 'acqua';
}
}
}
return 'gas_luce';
}),
])
->action(function (FatturaElettronica $record, array $data): void {
$doc = Documento::query()
->where('documentable_type', FatturaElettronica::class)
->where('documentable_id', $record->id)
->first();
if (! $doc || empty($doc->contenuto_ocr)) {
Notification::make()
->title('Testo OCR non disponibile')
->body('Esegui prima l\'azione OCR sul documento.')
->warning()
->send();
return;
}
$text = (string) $doc->contenuto_ocr;
$userId = (int) Auth::id();
if ($data['tipo_utenza'] === 'acqua') {
$parsed = app(AcquaPdfTextParser::class)->parse($text);
$ingestion = app(ConsumiAcquaIngestionService::class)->ingest(
$record,
$parsed,
null,
$userId,
true
);
$record->update([
'acqua_codice_utenza' => $parsed['codici']['utenza'] ?? null,
'acqua_codice_cliente' => $parsed['codici']['cliente'] ?? null,
'acqua_codice_contratto' => $parsed['codici']['contratto'] ?? null,
'acqua_contatore_matricola' => $parsed['contatore']['matricola'] ?? null,
'consumo_valore' => isset($parsed['consumi'][0]['valore']) ? $parsed['consumi'][0]['valore'] : null,
'consumo_unita' => 'mc',
'consumo_raw' => json_encode($parsed),
]);
} else {
$parsed = app(GasLucePdfTextParser::class)->parse($text);
$ingestion = app(ConsumiRiscaldamentoIngestionService::class)->ingest(
$record,
$parsed,
null,
$userId,
true
);
$record->update([
'acqua_codice_utenza' => $parsed['codici']['pdr'] ?? ($parsed['codici']['pod'] ?? null),
'acqua_codice_cliente' => $parsed['codici']['cliente'] ?? null,
'acqua_codice_contratto' => $parsed['codici']['contratto'] ?? null,
'acqua_contatore_matricola' => $parsed['codici']['pdr'] ?? ($parsed['codici']['pod'] ?? null),
'consumo_valore' => isset($parsed['consumi'][0]['valore']) ? $parsed['consumi'][0]['valore'] : null,
'consumo_unita' => $parsed['codici']['pod'] ? 'kWh' : 'Smc',
'consumo_raw' => json_encode($parsed),
]);
}
Notification::make()
->title('Dati estratti e allineati')
->body('Servizio: ' . ($ingestion['status'] ?? 'n/d') . ' - Letture create: ' . ($ingestion['created_letture'] ?? 0))
->success()
->send();
}),
TableAction::make('modifica_utenza')
->label('Modifica Dati')
->icon('heroicon-o-pencil')
->color('success')
->mountUsing(function (TextInput $codice_cliente, TextInput $codice_contratto, TextInput $pdr_pod, TextInput $consumo_valore, Select $consumo_unita, FatturaElettronica $record) {
$raw = is_string($record->consumo_raw) ? json_decode($record->consumo_raw, true) : null;
$pdrVal = $record->acqua_contatore_matricola;
$cliVal = $record->acqua_codice_cliente;
$conVal = $record->acqua_codice_contratto;
if ($raw && isset($raw['codici'])) {
$pdrVal = $raw['codici']['pdr'] ?? ($raw['codici']['pod'] ?? ($raw['codici']['utenza'] ?? $pdrVal));
$cliVal = $raw['codici']['cliente'] ?? $cliVal;
$conVal = $raw['codici']['contratto'] ?? $conVal;
}
$codice_cliente->state($cliVal);
$codice_contratto->state($conVal);
$pdr_pod->state($pdrVal);
$consumo_valore->state($record->consumo_valore);
$consumo_unita->state($record->consumo_unita ?: 'Smc');
})
->form([
TextInput::make('codice_cliente')->label('Codice Cliente'),
TextInput::make('codice_contratto')->label('Codice Contratto'),
TextInput::make('pdr_pod')->label('Matricola / POD / PDR'),
TextInput::make('consumo_valore')->label('Valore Consumo')->numeric(),
Select::make('consumo_unita')
->label('Unità di Misura')
->options([
'Smc' => 'Smc (Gas)',
'mc' => 'mc (Acqua)',
'kWh' => 'kWh (Energia)',
]),
])
->action(function (FatturaElettronica $record, array $data): void {
$raw = is_string($record->consumo_raw) ? json_decode($record->consumo_raw, true) : [];
if (!is_array($raw)) {
$raw = [];
}
$raw['codici'] = [
'cliente' => $data['codice_cliente'],
'contratto' => $data['codice_contratto'],
'pdr' => $data['consumo_unita'] === 'Smc' ? $data['pdr_pod'] : null,
'pod' => $data['consumo_unita'] === 'kWh' ? $data['pdr_pod'] : null,
'utenza' => $data['consumo_unita'] === 'mc' ? $data['pdr_pod'] : null,
];
$record->update([
'acqua_codice_cliente' => $data['codice_cliente'],
'acqua_codice_contratto' => $data['codice_contratto'],
'acqua_codice_utenza' => $data['pdr_pod'],
'acqua_contatore_matricola' => $data['pdr_pod'],
'consumo_valore' => $data['consumo_valore'],
'consumo_unita' => $data['consumo_unita'],
'consumo_raw' => json_encode($raw),
]);
// Forza sincronizzazione della lettura collegata
$userId = (int) Auth::id();
if ($data['consumo_unita'] === 'mc') {
app(ConsumiAcquaIngestionService::class)->ingest($record, $raw, null, $userId, true);
} else {
app(ConsumiRiscaldamentoIngestionService::class)->ingest($record, $raw, null, $userId, true);
}
Notification::make()
->title('Dati utenza aggiornati e sincronizzati con successo')
->success()
->send();
}),
]);
}
}