diff --git a/app/Filament/Resources/PricelistFiles/Pages/EditPricelistFile.php b/app/Filament/Resources/PricelistFiles/Pages/EditPricelistFile.php
index e48f98f..ccc1bdb 100644
--- a/app/Filament/Resources/PricelistFiles/Pages/EditPricelistFile.php
+++ b/app/Filament/Resources/PricelistFiles/Pages/EditPricelistFile.php
@@ -2,7 +2,9 @@
namespace App\Filament\Resources\PricelistFiles\Pages;
+use App\Enums\PricelistFileStatusEnum;
use App\Filament\Resources\PricelistFiles\PricelistFileResource;
+use App\Services\PricelistFileProcessService;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
@@ -22,4 +24,11 @@ protected function getHeaderActions(): array
RestoreAction::make(),
];
}
+
+ protected function afterSave(): void
+ {
+ if ($this->record->status === PricelistFileStatusEnum::fail) {
+ app(PricelistFileProcessService::class)->dispatchInitialChain($this->record);
+ }
+ }
}
diff --git a/app/Filament/Resources/PricelistFiles/Pages/ViewPricelistFile.php b/app/Filament/Resources/PricelistFiles/Pages/ViewPricelistFile.php
index 4bd41e0..fdc5bc9 100644
--- a/app/Filament/Resources/PricelistFiles/Pages/ViewPricelistFile.php
+++ b/app/Filament/Resources/PricelistFiles/Pages/ViewPricelistFile.php
@@ -5,6 +5,7 @@
use App\Filament\Resources\PricelistFiles\PricelistFileResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
+use App\Enums\PricelistFileStatusEnum;
class ViewPricelistFile extends ViewRecord
{
@@ -13,7 +14,8 @@ class ViewPricelistFile extends ViewRecord
protected function getHeaderActions(): array
{
return [
- EditAction::make(),
+ EditAction::make()
+ ->visible(fn ($record) => $record->status === PricelistFileStatusEnum::fail),
];
}
diff --git a/app/Filament/Resources/PricelistFiles/PricelistFileResource.php b/app/Filament/Resources/PricelistFiles/PricelistFileResource.php
index 483e4bf..579e9e9 100644
--- a/app/Filament/Resources/PricelistFiles/PricelistFileResource.php
+++ b/app/Filament/Resources/PricelistFiles/PricelistFileResource.php
@@ -15,7 +15,7 @@
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
-use Filament\Support\Enums\MaxWidth;
+use Filament\Support\Enums\Width;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
@@ -33,9 +33,9 @@ class PricelistFileResource extends Resource
protected static ?string $modelLabel = 'Árlista fájl';
- public static function getMaxWidth(): MaxWidth|string|null
+ public static function getMaxWidth(): Width|string|null
{
- return MaxWidth::Full;
+ return Width::Full;
}
public static function form(Schema $schema): Schema
diff --git a/app/Filament/Resources/PricelistFiles/RelationManagers/LinesRelationManager.php b/app/Filament/Resources/PricelistFiles/RelationManagers/LinesRelationManager.php
index 39e923d..4d61518 100644
--- a/app/Filament/Resources/PricelistFiles/RelationManagers/LinesRelationManager.php
+++ b/app/Filament/Resources/PricelistFiles/RelationManagers/LinesRelationManager.php
@@ -4,18 +4,25 @@
use App\Enums\PricelistFileLineStatusEnum;
use App\Enums\PricelistFileStatusEnum;
+use App\Services\PriceListService;
use Filament\Actions\ViewAction;
-use Filament\Forms\Components\KeyValue;
-use Filament\Forms\Components\Placeholder;
use Filament\Infolists\Components\TextEntry;
use Filament\Resources\RelationManagers\RelationManager;
+use Filament\Schemas\Components\EmbeddedTable;
use Filament\Schemas\Components\Grid;
+use Filament\Schemas\Components\RenderHook;
use Filament\Schemas\Components\Section;
+use Filament\Schemas\Components\Text;
use Filament\Schemas\Schema;
+use Filament\Support\Facades\FilamentView;
use Filament\Tables\Columns\TextColumn;
-use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
+use Filament\Tables\View\TablesRenderHook;
+use Filament\View\PanelsRenderHook;
+use Filament\Support\Enums\Width;
use Illuminate\Support\HtmlString;
+use Filament\Schemas\Components\Tabs\Tab;
+use Illuminate\Database\Eloquent\Builder;
class LinesRelationManager extends RelationManager
{
@@ -23,37 +30,135 @@ class LinesRelationManager extends RelationManager
protected static ?string $title = 'Feldolgozott sorok';
+ public function booted()
+ {
+ FilamentView::registerRenderHook(
+ TablesRenderHook::TOOLBAR_START,
+ fn () => $this->getTabsContentComponent()->container($this->makeSchema())->toHtml(),
+ scopes: static::class,
+ );
+ }
+
+ public function content(Schema $schema): Schema
+ {
+ return $schema
+ ->components([
+ RenderHook::make(PanelsRenderHook::RESOURCE_RELATION_MANAGER_BEFORE),
+ EmbeddedTable::make(),
+ RenderHook::make(PanelsRenderHook::RESOURCE_RELATION_MANAGER_AFTER),
+ ]);
+ }
+
+ public function getTabs(): array
+ {
+ $ownerRecord = $this->getOwnerRecord();
+ $counts = $ownerRecord->lines()
+ ->selectRaw('status, count(*) as count')
+ ->groupBy('status')
+ ->pluck('count', 'status');
+
+ return [
+ 'all' => Tab::make('Mind')
+ ->badge($counts->sum()),
+ 'hibas' => Tab::make('Hibás')
+ ->icon(PricelistFileLineStatusEnum::error->icon())
+ ->badgeColor(PricelistFileLineStatusEnum::error->color())
+ ->badge($counts->get(PricelistFileLineStatusEnum::error->value, 0))
+ ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::error)),
+ 'warning' => Tab::make('Figyelmeztetések')
+ ->icon(PricelistFileLineStatusEnum::warning->icon())
+ ->badgeColor(PricelistFileLineStatusEnum::warning->color())
+ ->badge($counts->get(PricelistFileLineStatusEnum::warning->value, 0))
+ ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::warning)),
+ 'uj' => Tab::make('Új')
+ ->icon(PricelistFileLineStatusEnum::new_product->icon())
+ ->badgeColor(PricelistFileLineStatusEnum::new_product->color())
+ ->badge($counts->get(PricelistFileLineStatusEnum::new_product->value, 0))
+ ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::new_product)),
+ 'modosult' => Tab::make('Módosult')
+ ->icon(PricelistFileLineStatusEnum::updated->icon())
+ ->badgeColor(PricelistFileLineStatusEnum::updated->color())
+ ->badge($counts->get(PricelistFileLineStatusEnum::updated->value, 0))
+ ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PricelistFileLineStatusEnum::updated)),
+ ];
+ }
+
public function form(Schema $schema): Schema
{
return $schema
->components([
- Grid::make(2)
+ Grid::make(1)
->schema([
- TextEntry::make('row_number')
- ->label('Sor száma'),
- TextEntry::make('status')
- ->label('Státusz')
- ->badge()
- ->color(fn (PricelistFileLineStatusEnum $state) => $state->color())
- ->formatStateUsing(fn (PricelistFileLineStatusEnum $state) => $state->label()),
- ]),
+ Section::make('Módosulások')
+ ->schema([
+ Text::make(fn ($record) => new HtmlString(
+ '
' .
+ collect($record->diff ?? [])->map(function ($d, $field) {
+ $old = $d['old_label'] ?? $d['old'] ?? 'N/A';
+ $new = $d['new_label'] ?? $d['new'] ?? 'N/A';
+ $label = $d['label'] ?? $field;
+
+ if ($old === '' || $old === null) $old = '
(üres)';
+ if ($new === '' || $new === null) $new = '
(üres)';
+
+ // Ár formázása ha árról van szó
+ if ($field === 'price') {
+ $old = number_format((float)($d['old'] ?? 0), 2, ',', ' ') . ' Ft';
+ $new = number_format((float)($d['new'] ?? 0), 2, ',', ' ') . ' Ft';
+ }
+
+ return "
+
$label
+
+ $old
+ →
+ $new
+
+
";
+ })->join('') .
+ '
'
+ )),
+ ])
+ ->visible(fn ($record) => !empty($record->diff)),
+
+ Section::make('Validációs üzenetek')
+ ->schema([
+ Text::make(fn ($record) => new HtmlString(
+ '' .
+ collect($record->validation_messages ?? [])->map(fn ($msg) => "- $msg
")->join('') .
+ '
'
+ )),
+ ])
+ ->visible(fn ($record) => !empty($record->validation_messages)),
+ ])
+ ->columnSpan(1)
+ ->visible(fn ($record) => !empty($record->diff) || !empty($record->validation_messages)),
+
Section::make('Adatok')
->schema([
- KeyValue::make('payload')
- ->label('Excel adatok')
- ->columnSpanFull(),
- ]),
- Section::make('Validációs üzenetek')
- ->schema([
- Placeholder::make('validation_messages_list')
- ->label(false)
- ->content(fn ($record) => new HtmlString(
- '' .
- collect($record->validation_messages ?? [])->map(fn ($msg) => "- $msg
")->join('') .
- '
'
- )),
+ Text::make(fn ($record) => new HtmlString(
+ '
+
+
+
+ | Oszlop |
+ Érték |
+
+
+
+ ' . collect($record->payload ?? [])->map(function ($value, $key) {
+ $valStr = is_array($value) ? json_encode($value) : (string)$value;
+ return '
+ | ' . e($key) . ' |
+ ' . e($valStr) . ' |
+
';
+ })->join('') . '
+
+
+ '
+ )),
])
- ->visible(fn ($record) => !empty($record->validation_messages)),
+ ->columnSpan(fn ($record) => (empty($record->diff) && empty($record->validation_messages)) ? 2 : 1),
]);
}
@@ -77,25 +182,41 @@ public function table(Table $table): Table
TextColumn::make('product_info')
->label('Termék')
- ->description(fn ($record) => $record->payload['Termék megnevezése'] ?? '')
- ->getStateUsing(fn ($record) => $record->payload['Szállítói cikkszám'] ?? 'N/A')
+ ->description(fn ($record) => $record->payload[PriceListService::EXPECTED_HEADERS[4]] ?? '')
+ ->getStateUsing(fn ($record) => $record->payload[PriceListService::EXPECTED_HEADERS[0]] ?? 'N/A')
+ ->color(fn ($record) => !empty($record->diff) ? 'info' : null)
+ ->wrap()
->searchable(query: function ($query, string $search) {
- $query->where('payload->Szállítói cikkszám', 'like', "%{$search}%")
- ->orWhere('payload->Termék megnevezése', 'like', "%{$search}%");
+ $sku = PriceListService::EXPECTED_HEADERS[0];
+ $name = PriceListService::EXPECTED_HEADERS[4];
+ $query->where("payload->$sku", 'like', "%{$search}%")
+ ->orWhere("payload->$name", 'like', "%{$search}%");
}),
+
TextColumn::make('price_diff')
->label('Árváltozás')
->html()
->getStateUsing(function ($record) {
- $oldPrice = $record->diff['changes']['price']['old'] ?? null;
- $newPrice = $record->payload['Új számlázási nettó ár'] ?? null;
+ // Biztonságos elérés data_get-tel, mert a diff lehet null
+ $oldPrice = data_get($record->diff, 'price.old');
- if ($oldPrice === null) {
- return number_format((float)$newPrice, 2, ',', ' ') . ' Ft';
+ // Az új árat a diff-ből vesszük (már float), vagy a payload-ból (string lehet)
+ $newPriceRaw = data_get($record->diff, 'price.new') ?? data_get($record->payload, PriceListService::EXPECTED_HEADERS[14]);
+
+ // Normalizálás: szóközök eltávolítása és tizedesvessző cseréje
+ $newPrice = is_string($newPriceRaw)
+ ? (float) str_replace([' ', ','], ['', '.'], $newPriceRaw)
+ : (float) $newPriceRaw;
+
+ // Ha nincs régi ár, vagy az nulla, akkor nincs százalékos számítás
+ if (empty($oldPrice) || (float)$oldPrice === 0.0) {
+ return number_format($newPrice, 2, ',', ' ') . ' Ft';
}
+ $oldPrice = (float) $oldPrice;
$diffPercent = (($newPrice / $oldPrice) - 1) * 100;
+
$colorClass = $diffPercent > 0 ? 'text-danger-600' : ($diffPercent < 0 ? 'text-success-600' : 'text-gray-600');
$icon = $diffPercent > 0 ? '↑' : ($diffPercent < 0 ? '↓' : '');
@@ -105,7 +226,7 @@ public function table(Table $table): Table
%s Ft %s %s%%
',
number_format($oldPrice, 2, ',', ' '),
- number_format((float)$newPrice, 2, ',', ' '),
+ number_format($newPrice, 2, ',', ' '),
$colorClass,
$icon,
number_format(abs($diffPercent), 1, ',', ' ')
@@ -117,20 +238,40 @@ public function table(Table $table): Table
->label('Megjegyzés')
->listWithLineBreaks()
->bulleted()
+ ->wrap()
->color(fn ($state) => collect($state)->contains(fn ($msg) => str_contains($msg, 'Hiányzó') || str_contains($msg, 'Ismeretlen') || str_contains($msg, 'Érvénytelen')) ? 'danger' : 'info')
- ->getStateUsing(fn ($record) => $record->validation_messages ?? []),
+ ->getStateUsing(function ($record) {
+ $messages = collect($record->validation_messages ?? [])->values();
+
+ $diffMessages = collect($record->diff ?? [])->map(function ($d, $field) {
+ $label = $d['label'] ?? $field;
+ $old = $d['old_label'] ?? $d['old'] ?? 'N/A';
+ $new = $d['new_label'] ?? $d['new'] ?? 'N/A';
+
+ if ($field === 'price') {
+ $old = number_format((float)($d['old'] ?? 0), 0, ',', ' ') . ' Ft';
+ $new = number_format((float)($d['new'] ?? 0), 0, ',', ' ') . ' Ft';
+ }
+
+ return "Módosult $label: $old -> $new";
+ })->values();
+
+ return $messages->merge($diffMessages)->toArray();
+ }),
])
->filters([
- SelectFilter::make('status')
- ->label('Státusz')
- ->options(collect(PricelistFileLineStatusEnum::cases())->mapWithKeys(fn ($e) => [$e->value => $e->label()])),
+ //
])
->headerActions([
//
])
->actions([
ViewAction::make()
- ->slideOver(),
+ ->slideOver()
+ ->modalWidth(Width::FourExtraLarge)
+ ->iconButton()
+ ->modalHeading(fn ($record) => "Sor száma: #{$record->row_number}")
+ ->modalDescription(fn ($record) => "Státusz: " . $record->status->label()),
])
->bulkActions([
//
diff --git a/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileForm.php b/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileForm.php
index 2e3bfbf..df190ff 100644
--- a/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileForm.php
+++ b/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileForm.php
@@ -2,6 +2,13 @@
namespace App\Filament\Resources\PricelistFiles\Schemas;
+use App\Models\Supplier;
+use Filament\Forms\Components\DatePicker;
+use Filament\Forms\Components\FileUpload;
+use Filament\Forms\Components\Select;
+use Filament\Forms\Components\Textarea;
+use Filament\Schemas\Components\Grid;
+use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class PricelistFileForm
@@ -10,7 +17,32 @@ public static function configure(Schema $schema): Schema
{
return $schema
->components([
- //
+ Section::make('Árlista fájl adatai')
+ ->schema([
+ Grid::make(2)
+ ->schema([
+ Select::make('supplier_id')
+ ->label('Beszállító')
+ ->options(Supplier::query()->pluck('name', 'id'))
+ ->required()
+ ->searchable(),
+ DatePicker::make('available_date')
+ ->label('Életbelépés dátuma')
+ ->required()
+ ->native(false)
+ ->displayFormat('Y.m.d'),
+ ]),
+ Textarea::make('note')
+ ->label('Megjegyzés')
+ ->rows(3),
+ FileUpload::make('filename')
+ ->label('Fájl kiválasztása')
+ ->required()
+ ->acceptedFileTypes(['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv'])
+ ->disk('public')
+ ->directory('pricelist-uploads')
+ ->preserveFilenames(),
+ ])
]);
}
}
diff --git a/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileInfolist.php b/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileInfolist.php
index 0413c7a..d2e25b7 100644
--- a/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileInfolist.php
+++ b/app/Filament/Resources/PricelistFiles/Schemas/PricelistFileInfolist.php
@@ -63,7 +63,7 @@ public static function configure(Schema $schema): Schema
TextEntry::make('processing_current_step')
->label('Aktuális folyamat')
->weight('bold')
- ->color('info')
+ ->color(fn ($record) => $record->status === PricelistFileStatusEnum::fail ? 'danger' : 'info')
->formatStateUsing(fn ($state, $record) => "$state ({$record->processing_current_step_percentage}%)"),
]),
TextEntry::make('note')
diff --git a/app/Filament/Resources/PricelistFiles/Tables/PricelistFilesTable.php b/app/Filament/Resources/PricelistFiles/Tables/PricelistFilesTable.php
index ac47b32..0e0e125 100644
--- a/app/Filament/Resources/PricelistFiles/Tables/PricelistFilesTable.php
+++ b/app/Filament/Resources/PricelistFiles/Tables/PricelistFilesTable.php
@@ -58,7 +58,8 @@ public static function configure(Table $table): Table
])
->recordActions([
ViewAction::make(),
- EditAction::make(),
+ EditAction::make()
+ ->visible(fn ($record) => $record->status === PricelistFileStatusEnum::fail),
])
->toolbarActions([
BulkActionGroup::make([
diff --git a/app/Services/PricelistFileProcessService.php b/app/Services/PricelistFileProcessService.php
index 900bd84..fe7efd3 100644
--- a/app/Services/PricelistFileProcessService.php
+++ b/app/Services/PricelistFileProcessService.php
@@ -21,6 +21,49 @@
class PricelistFileProcessService
{
+ protected const PRODUCT_FIELD_MAP = [
+ 'name' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[4], // Termék megnevezése
+ 'type' => 'string'
+ ],
+ 'unitValue' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[6], // Súly/Űrtartalom (nettó)
+ 'type' => 'float'
+ ],
+ 'productUnit' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[7], // Mértékegység
+ 'type' => 'string'
+ ],
+ 'sellerUnit' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[9], // Legkisebb eladási egység
+ 'type' => 'string'
+ ],
+ 'unitMultiplier' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[10], // Egységszorzó
+ 'type' => 'float'
+ ],
+ 'amountUnit' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[11], // Mennyiségi egység
+ 'type' => 'string'
+ ],
+ 'vat' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[12], // ÁFA %
+ 'type' => 'float'
+ ],
+ 'hooreycaId' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[16], // Fix Hooreyca ID
+ 'type' => 'string'
+ ],
+ 'HooreycaUnit' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[17], // Hooreyca mértékegység
+ 'type' => 'string'
+ ],
+ 'HooreycaMultiplier' => [
+ 'header' => PriceListService::EXPECTED_HEADERS[18], // Hooreyca mennyiségi szorzó
+ 'type' => 'float'
+ ],
+ ];
+
public function __construct(
protected PriceListService $priceListService
) {}
@@ -455,7 +498,26 @@ public function validate(PricelistFile $pricelistFile): bool
$hasData = false;
foreach ($expectedHeaders as $index => $label) {
- $value = $worksheet->getCellByColumnAndRow($index + 1, $row)->getValue();
+ $cell = $worksheet->getCellByColumnAndRow($index + 1, $row);
+ $value = $cell->getValue();
+
+ // Ha képlet, próbáljuk kiszámolni a megjelenített értéket
+ if (str_starts_with((string)$value, "=")) {
+ try {
+ // Megpróbáljuk kiszámítani a képletet
+ $value = $cell->getCalculatedValue();
+ } catch (\Exception $e) {
+ try {
+ // Ha a számítás nem sikerül (pl. hiányzó külső hivatkozás),
+ // próbáljuk a fájlban utoljára tárolt értéket (cache) lekérni
+ $value = $cell->getOldCalculatedValue();
+ } catch (\Exception $e2) {
+ // Ha nincs korábbi érték sem, marad a képlet szövege fallback-ként
+ \Illuminate\Support\Facades\Log::warning("Képlet számítási hiba a(z) {$row}. sorban: " . $e->getMessage());
+ }
+ }
+ }
+
$rowData[$label] = $value;
if (!empty(trim((string)$value))) {
$hasData = true;
@@ -495,6 +557,21 @@ public function validate(PricelistFile $pricelistFile): bool
$this->runBusinessValidation($pricelistFile);
$duration = round(microtime(true) - $startTime, 2);
+
+ // Ellenőrizzük, vannak-e hibás sorok
+ $hasErrors = $pricelistFile->lines()->where('status', PricelistFileLineStatusEnum::error)->exists();
+
+ if ($hasErrors) {
+ $this->updateStepStatus(
+ $pricelistFile,
+ PricelistWorkflowStep::Validation,
+ 'failed',
+ "befejeződött, de hibás sorokat találtunk. Kérjük, javítsa a hibákat a továbblépéshez!",
+ 100
+ );
+ return false;
+ }
+
$this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'completed', "Sikeres validálás ({$processedCount} sor, {$duration} mp).", 100);
// A validálás végén átadjuk a lépést a jóváhagyásnak (Approval)
@@ -562,6 +639,36 @@ protected function getProducerLookupMap(): array
->toArray();
}
+ /**
+ * Gyártó ID -> név mapping
+ */
+ protected function getProducerIdMap(): array
+ {
+ return \App\Models\Producer::pluck('name', 'id')->toArray();
+ }
+
+ /**
+ * Termékcsoport ID -> útvonal mapping
+ */
+ protected function getProductGroupIdMap(): array
+ {
+ $allGroups = (new \App\Models\ProductGroup)->allWithPath();
+ $idMap = [];
+
+ foreach ($allGroups as $group) {
+ $parentsNames = $group['parentsNames'] ?? [];
+
+ if (isset($group['depth']) && $group['depth'] > 0 && !empty($parentsNames)) {
+ array_shift($parentsNames);
+ }
+
+ $pathParts = array_merge($parentsNames, [$group['name']]);
+ $idMap[$group['id']] = implode(' > ', $pathParts);
+ }
+
+ return $idMap;
+ }
+
/**
* Termék lookup tábla felépítése a beszállítóhoz (cikkszám -> id)
*/
@@ -726,13 +833,59 @@ protected function runBusinessValidation(PricelistFile $pricelistFile): void
$producerLookup = $this->getProducerLookupMap();
$productLookup = $this->getProductLookupMap($pricelistFile->supplier_id);
- $pricelistFile->lines()->chunk(1000, function ($lines) use ($groupLookup, $producerLookup, $productLookup) {
+ $producerIdToName = $this->getProducerIdMap();
+ $groupIdToPath = $this->getProductGroupIdMap();
+
+ $pricelistFile->lines()->chunk(1000, function ($lines) use ($groupLookup, $producerLookup, $productLookup, $producerIdToName, $groupIdToPath) {
$updates = [];
foreach ($lines as $line) {
$payload = $line->payload;
$validationMessages = [];
+ // 1. Kötelező mezők ellenőrzése (0-12 és 14)
+ $mandatoryIndexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14];
+ foreach ($mandatoryIndexes as $mIndex) {
+ $mLabel = PriceListService::EXPECTED_HEADERS[$mIndex];
+ $mValue = $payload[$mLabel] ?? null;
+ if ($mValue === null || (is_string($mValue) && trim($mValue) === '')) {
+ // Megfelelő kulcsot választunk, hogy a specifikus validátorok felülírhassák
+ $mKey = match($mIndex) {
+ 7 => 'product_unit',
+ 8 => 'producer',
+ 9 => 'seller_unit',
+ 11 => 'amount_unit',
+ 12 => 'vat',
+ default => 'mandatory_' . $mIndex
+ };
+ $validationMessages[$mKey] = "Hiányzó kötelező mező: $mLabel";
+ }
+ }
+
+ // 2. Hooreyca specifikus mezők függősége (16, 17, 18, 19)
+ $hooreycaIndexes = [16, 17, 18, 19];
+ $hasAnyHooreyca = false;
+ $hooreycaData = [];
+ foreach ($hooreycaIndexes as $hIndex) {
+ $hLabel = PriceListService::EXPECTED_HEADERS[$hIndex];
+ $hValue = $payload[$hLabel] ?? null;
+ if ($hValue !== null && !(is_string($hValue) && trim($hValue) === '')) {
+ $hasAnyHooreyca = true;
+ $hooreycaData[$hIndex] = $hValue;
+ } else {
+ $hooreycaData[$hIndex] = null;
+ }
+ }
+
+ if ($hasAnyHooreyca) {
+ foreach ($hooreycaIndexes as $hIndex) {
+ if ($hooreycaData[$hIndex] === null) {
+ $hLabel = PriceListService::EXPECTED_HEADERS[$hIndex];
+ $validationMessages['hooreyca_' . $hIndex] = "Hiányzó kötelező mező (Hooreyca adatok jelenléte miatt): $hLabel";
+ }
+ }
+ }
+
// Termék ID kinyerése (cikkszám alapján)
$sku = (string)($payload[PriceListService::EXPECTED_HEADERS[0]] ?? '');
$existingProduct = $productLookup[$sku] ?? null;
@@ -788,6 +941,8 @@ protected function runBusinessValidation(PricelistFile $pricelistFile): void
}
$status = PricelistFileLineStatusEnum::ok;
+ $diff = [];
+
if ($hasError) {
$status = PricelistFileLineStatusEnum::error;
} elseif ($productId === null) {
@@ -798,22 +953,97 @@ protected function runBusinessValidation(PricelistFile $pricelistFile): void
$isUpdated = false;
// 1. Gyártó módosulás
+ $oldProducerId = (int)($existingProduct['producer_id'] ?? 0);
if ($hasNewProducer) {
$isUpdated = true;
- } elseif ($producerId !== null && (int)($existingProduct['producer_id'] ?? 0) !== (int)$producerId) {
+ $diff['producer_id'] = [
+ 'old' => $oldProducerId,
+ 'new' => $producerId,
+ 'old_label' => $producerIdToName[$oldProducerId] ?? ($oldProducerId > 0 ? "Ismeretlen gyártó ($oldProducerId)" : 'N/A'),
+ 'new_label' => $producerName,
+ 'label' => PriceListService::EXPECTED_HEADERS[8]
+ ];
+ } elseif ($producerId !== null && $oldProducerId !== (int)$producerId) {
$isUpdated = true;
- $validationMessages['producer_update'] = "Módosult gyártó";
+ $diff['producer_id'] = [
+ 'old' => $oldProducerId,
+ 'new' => (int)$producerId,
+ 'old_label' => $producerIdToName[$oldProducerId] ?? ($oldProducerId > 0 ? "Ismeretlen gyártó ($oldProducerId)" : 'N/A'),
+ 'new_label' => $producerIdToName[(int)$producerId] ?? ($producerId > 0 ? "Ismeretlen gyártó ($producerId)" : 'N/A'),
+ 'label' => PriceListService::EXPECTED_HEADERS[8]
+ ];
}
// 2. Termékcsoport módosulás
- if ($groupId !== null && (int)($existingProduct['product_group_id'] ?? 0) !== (int)$groupId) {
+ $oldGroupId = (int)($existingProduct['product_group_id'] ?? 0);
+ if ($groupId !== null && $oldGroupId !== (int)$groupId) {
$isUpdated = true;
- $validationMessages['product_group_update'] = "Módosult termékcsoport";
+ $oldPath = $groupIdToPath[$oldGroupId] ?? null;
+ $newPath = $groupIdToPath[(int)$groupId] ?? null;
+
+ $diff['product_group_id'] = [
+ 'old' => $oldGroupId,
+ 'new' => (int)$groupId,
+ 'old_label' => $oldPath ?? ($oldGroupId > 0 ? "Ismeretlen csoport ($oldGroupId)" : 'N/A'),
+ 'new_label' => $newPath ?? ($groupId > 0 ? "Ismeretlen csoport ($groupId)" : 'N/A'),
+ 'label' => PriceListService::EXPECTED_HEADERS[1]
+ ];
+ }
+
+ // 3. Általános mezők összehasonlítása
+ foreach (self::PRODUCT_FIELD_MAP as $modelField => $config) {
+ $excelValue = $payload[$config['header']] ?? null;
+ $currentValue = $existingProduct[$modelField] ?? null;
+
+ // Típuskonverzió és normalizálás az összehasonlításhoz
+ if ($config['type'] === 'float') {
+ $excelValue = is_string($excelValue)
+ ? (float)str_replace([' ', ','], ['', '.'], $excelValue)
+ : (float)$excelValue;
+
+ // ÁFA speciális kezelése: 0.05 -> 5
+ if ($modelField === 'vat') {
+ if ($excelValue > 0 && $excelValue <= 1) {
+ $excelValue *= 100;
+ }
+ $excelValue = (float) round($excelValue);
+ }
+
+ $currentValue = (float)$currentValue;
+ }
+
+ if ($excelValue != $currentValue) {
+ $diff[$modelField] = [
+ 'old' => $currentValue,
+ 'new' => $excelValue,
+ 'label' => $config['header']
+ ];
+ $isUpdated = true;
+ }
}
if ($isUpdated) {
$status = PricelistFileLineStatusEnum::updated;
}
+
+ // 4. Ár módosulás (mindig bekerül a diff-be ha van változás, de nem feltétlenül váltja ki az 'updated' státuszt, ha az árat külön kezeljük)
+ // Megjegyzés: Az áráltozás önmagában is lehet 'updated', de sokszor az árlista import lényege az árváltozás.
+ $oldPriceExcel = $payload[PriceListService::EXPECTED_HEADERS[13]] ?? null;
+ $newPriceExcel = $payload[PriceListService::EXPECTED_HEADERS[14]] ?? null;
+
+ if ($oldPriceExcel !== null && $newPriceExcel !== null) {
+ // Szóközök és vesszők egységes kezelése string típus esetén
+ $oldPrice = (float)str_replace([' ', ','], ['', '.'], (string)$oldPriceExcel);
+ $newPrice = (float)str_replace([' ', ','], ['', '.'], (string)$newPriceExcel);
+
+ if ($oldPrice != $newPrice) {
+ $diff['price'] = [
+ 'old' => $oldPrice,
+ 'new' => $newPrice,
+ 'label' => PriceListService::EXPECTED_HEADERS[14]
+ ];
+ }
+ }
}
$updates[] = [
@@ -826,6 +1056,7 @@ protected function runBusinessValidation(PricelistFile $pricelistFile): void
'producer_id' => $producerId,
'payload' => json_encode($line->payload),
'validation_messages' => json_encode($validationMessages),
+ 'diff' => json_encode($diff),
'created_at' => $line->created_at->toDateTimeString(),
'updated_at' => now()->toDateTimeString(),
];
@@ -836,7 +1067,7 @@ protected function runBusinessValidation(PricelistFile $pricelistFile): void
PricelistFileLine::upsert(
$updates,
['id'], // Egyedi azonosító
- ['status', 'validation_messages', 'product_id', 'product_group_id', 'producer_id', 'updated_at'] // Frissítendő mezők
+ ['status', 'validation_messages', 'diff', 'product_id', 'product_group_id', 'producer_id', 'updated_at'] // Frissítendő mezők
);
}
});