d2d.emegrendeles.hu/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileInfolist.php
E98Developer e338f68ee8 ADD EV3-357 Árlista feldolgozás phase5/4 visszavonás, elakadás-észlelés, kényszerlezárás
- PricelistExecutionJob + új PricelistRevertJob: $tries=1, $timeout, failed()
  hook. A service catch ága csak PHP exceptiont fog el; worker timeout vagy
  memórialimit esetén a fájl inprogress-ben ragadna, döntési gomb nélkül.
- heartbeat backstop a kill -9 esetére, ahol a failed() sem fut: minden chunk ír
  a rekordba, így az updated_at a szívverés (isExecutionStuck). Ezzel garantált,
  hogy minden kísérlet véges időn belül terminális állapotba jut - enélkül a
  kényszerlezárás sem nyílna ki soha.
- kompenzáló visszaállítás R1-R4: árak törlése, termékek visszaállítása a
  snapshotból, létrehozott termékek kivonása (soft delete, NEM fizikai törlés,
  mert lehet rájuk hivatkozás), árlista + fájl lezárása
- konfliktuskezelés: a végrehajtás óta kézzel módosított terméket kihagyjuk és
  jelentjük, nem írjuk felül vakon
- FIX phase5/3: a snapshot csak az írás ELŐTTI updated_at-et tárolta, amihez
  képest a termék a végrehajtás után mindig eltér - a konfliktus-ellenőrzés így
  minden sort kihagyott volna. Most a saját írásunk utáni updated_at is bekerül.
- kényszerlezárás: csak legalább egy terminális hibába futott visszaállítási
  kísérlet után, flag + developer szerepkör mögött, kötelező indoklással
- döntéstámogató panel: mi történt már meg az importból, hogy a felhasználó ne
  vakon válasszon a Folytatás és a Visszavonás között

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 06:36:21 +02:00

242 lines
17 KiB
PHP

<?php
namespace App\Filament\Resources\PricelistFiles\Schemas;
use App\Enums\PricelistFileStatusEnum;
use App\Models\PricelistFile;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Text;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
use Filament\Support\Enums\FontWeight;
use Filament\Support\Enums\IconPosition;
use Filament\Support\Enums\Width;
class PricelistFileInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
// Döntéstámogató panel: megszakadt végrehajtás után a felhasználónak a
// Folytatás és a Visszavonás között kell választania. Enélkül vakon
// döntene - itt látja, mi történt már meg az importból.
Section::make('Megszakadt végrehajtás')
->description('A végrehajtás nem fejeződött be. Az árlista nem élesedett, tehát a felhasználók felé nem került ki ár. Döntsd el, folytatod-e, vagy visszavonod a már végrehajtott részt.')
->icon('heroicon-o-exclamation-triangle')
->columnSpanFull()
->maxWidth(Width::Full)
->visible(fn ($record) => $record->needsExecutionDecision())
->schema([
TextEntry::make('execution_error')
->label('Hiba')
->color('danger')
->weight(FontWeight::Bold)
->columnSpanFull()
->getStateUsing(fn ($record) => $record->isExecutionStuck()
? 'A végrehajtás ' . PricelistFile::STUCK_AFTER_MINUTES . ' percnél régebben nem jelzett haladást (elakadt folyamat).'
: ($record->processing_current_step ?: 'Ismeretlen hiba.')),
Grid::make(4)->schema([
TextEntry::make('created_products')
->label('Létrehozott termék')
->icon('heroicon-o-plus-circle')
->getStateUsing(fn ($record) => $record->executionSummary()['created_products']),
TextEntry::make('updated_products')
->label('Frissített termék')
->icon('heroicon-o-pencil-square')
->getStateUsing(fn ($record) => $record->executionSummary()['updated_products']),
TextEntry::make('priced_lines')
->label('Rögzített ár')
->icon('heroicon-o-currency-dollar')
->getStateUsing(fn ($record) => $record->executionSummary()['priced_lines']),
TextEntry::make('pending_lines')
->label('Hátralévő sor')
->icon('heroicon-o-clock')
->getStateUsing(fn ($record) => $record->executionSummary()['pending_lines']),
]),
TextEntry::make('revert_error')
->label('Utolsó visszaállítási kísérlet hibája')
->color('danger')
->columnSpanFull()
->visible(fn ($record) => ($record->file_meta['revert']['last_status'] ?? null) === 'failed')
->getStateUsing(fn ($record) => $record->file_meta['revert']['last_error'] ?? null),
]),
Section::make(null)
->poll('5s')
->columnSpanFull()
->maxWidth(Width::Full)
->schema([
Grid::make(2)
->columnSpanFull()
->maxWidth(Width::Full)
->schema([
// BAL OSZLOP
Grid::make(1)
->columnSpan(1)
->schema([
Section::make('Fájl információk')
->schema([
Grid::make(3)
->schema([
TextEntry::make('filename')
->label('Fájlnév')
->icon('heroicon-o-document-text')
->color('primary'),
TextEntry::make('supplier.name')
->label('Beszállító')
->icon('heroicon-o-user'),
TextEntry::make('status')
->label('Státusz')
->badge()
->formatStateUsing(fn (PricelistFileStatusEnum $state): string => $state->label())
->color(fn (PricelistFileStatusEnum $state): string => $state->color()),
]),
Grid::make(3)
->schema([
TextEntry::make('available_date')
->label('Életbelépés')
->date('Y.m.d'),
TextEntry::make('created_at')
->label('Feltöltés')
->dateTime('Y.m.d H:i'),
]),
// SÁRGA NÉGYZET JAVÍTÁSA: Teljes szélességben alul, csak hiba esetén
TextEntry::make('processing_current_step')
->label('Validációs hiba')
->weight('bold')
->color('danger')
->columnSpanFull()
->visible(fn ($record) => $record->status === PricelistFileStatusEnum::fail)
->formatStateUsing(fn ($state, $record) => str_contains((string)$state, 'hibás') ? $state : "Hiba történt a folyamat során: $state"),
// Folyamatban lévő állapot kijelzése (nem hiba, de hasznos)
TextEntry::make('processing_current_step_inprogress')
->label('Aktuális folyamat')
->getStateUsing(fn ($record) => $record->processing_current_step)
->weight('bold')
->color('info')
->columnSpanFull()
->visible(fn ($record) => $record->status === PricelistFileStatusEnum::inprogress)
->formatStateUsing(fn ($state, $record) => "$state ({$record->processing_current_step_percentage}%)"),
TextEntry::make('note')
->label('Megjegyzés')
->placeholder('Nincs megjegyzés')
->columnSpanFull(),
]),
]),
// JOBB OSZLOP
Grid::make(1)
->columnSpan(1)
->schema([
Section::make('Fájl statisztika') // KÉK NÉGYZETEK JAVÍTÁSA: Összevont szekció
->schema([
Grid::make(5)->schema([
TextEntry::make('lines_count')
->label('Sorok')
->icon('heroicon-o-list-bullet')
->getStateUsing(fn ($record) => $record->lines()->count()),
TextEntry::make('error_lines_count')
->label('Hibás')
->icon('heroicon-o-x-circle')
->color('danger')
->getStateUsing(fn ($record) => $record->lines()->where('status', 'error')->count()),
TextEntry::make('warning_lines_count')
->label('Figyelm.')
->icon('heroicon-o-exclamation-triangle')
->color('warning')
->getStateUsing(fn ($record) => $record->lines()->where('status', 'warning')->count()),
TextEntry::make('new_lines_count')
->label('Új')
->icon('heroicon-o-sparkles')
->color('info')
->getStateUsing(fn ($record) => $record->lines()->where('status', 'new_product')->count()),
TextEntry::make('updated_lines_count')
->label('Módosult')
->icon('heroicon-o-pencil-square')
->color('primary')
->getStateUsing(fn ($record) => $record->lines()->where('status', 'updated')->count()),
]),
Grid::make(4)->schema([
TextEntry::make('file_meta.preprocess.statistics.data_rows')
->label('Adatsorok')
->icon('heroicon-o-table-cells'),
TextEntry::make('file_meta.preprocess.statistics.column_count')
->label('Oszlopok'),
TextEntry::make('file_meta.preprocess.file_info.file_size_human')
->label('Fájlméret'),
TextEntry::make('file_meta.preprocess.duration_seconds')
->label('Idő')
->suffix(' mp'),
])
->extraAttributes(['class' => 'mt-4 border-t pt-4']),
])
->visible(fn ($record) => $record->lines()->exists() || !empty($record->file_meta['preprocess'] ?? [])),
Section::make('Feldolgozási folyamat (Workflow)')
->collapsible()
->collapsed(fn ($record) => $record->status !== PricelistFileStatusEnum::inprogress)
->schema([
RepeatableEntry::make('workflow_steps')
->label(false)
->schema([
TextEntry::make('label')
->label('Lépés')
->weight('bold')
->inlineLabel(),
TextEntry::make('status')
->label('Állapot')
->badge()
->inlineLabel()
->formatStateUsing(fn (string $state): string => match ($state) {
'pending' => 'várakozik',
'inprogress' => 'folyamatban',
'completed' => 'kész',
'failed' => 'hiba',
default => $state,
})
->color(fn (string $state): string => match ($state) {
'pending' => 'gray',
'inprogress' => 'info',
'completed' => 'success',
'failed' => 'danger',
default => 'gray',
})
->icon(fn (string $state): string => match ($state) {
'pending' => 'heroicon-o-clock',
'inprogress' => 'heroicon-o-arrow-path',
'completed' => 'heroicon-o-check-circle',
'failed' => 'heroicon-o-x-circle',
default => 'heroicon-o-question-mark-circle',
}),
])
->columns(2)
->columnSpanFull(),
]),
RepeatableEntry::make('file_meta.preprocess.warnings')
->label('Előfeldolgozási figyelmeztetések')
->schema([
TextEntry::make('message')->label('Leírás')->color('warning'),
TextEntry::make('severity')->label('Súlyosság')->badge()->color('warning'),
])
->visible(fn ($record) => !empty($record->file_meta['preprocess']['warnings'] ?? [])),
RepeatableEntry::make('file_meta.preprocess.errors')
->label('Előfeldolgozási hibák')
->schema([
TextEntry::make('message')->label('Leírás')->color('danger'),
TextEntry::make('severity')->label('Súlyosság')->badge()->color('danger'),
])
->visible(fn ($record) => !empty($record->file_meta['preprocess']['errors'] ?? [])),
]),
]),
]),
]);
}
}