337 lines
17 KiB
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\Tables\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();
|
|
}),
|
|
]);
|
|
}
|
|
} |