367 lines
14 KiB
PHP
367 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Exports\CurrentPriceExport;
|
|
use App\Models\Producer;
|
|
use App\Models\Product;
|
|
use App\Models\ProductGroup;
|
|
use App\Models\Supplier;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Schemas\Components\Utilities\Get;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
|
|
class CurrentPrice extends Page implements HasForms
|
|
{
|
|
use InteractsWithForms;
|
|
|
|
/** @var int[] */
|
|
public const PER_PAGE_OPTIONS = [10, 25, 50, 100];
|
|
|
|
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-currency-dollar';
|
|
|
|
protected static ?string $title = 'Aktuális ár';
|
|
|
|
protected static ?string $navigationLabel = 'Aktuális ár';
|
|
|
|
protected static string | \UnitEnum | null $navigationGroup = 'Statisztika';
|
|
|
|
public function getHeading(): string|\Illuminate\Contracts\Support\Htmlable
|
|
{
|
|
return '';
|
|
}
|
|
|
|
public function getBreadcrumbs(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
protected string $view = 'filament.pages.current-price';
|
|
|
|
public ?array $data = [];
|
|
|
|
public bool $hasSearched = false;
|
|
|
|
public int $perPage = 10;
|
|
|
|
public int $page = 1;
|
|
|
|
public int $totalResults = 0;
|
|
|
|
/** @var array<int, array{supplier: ?string, producer: ?string, mainGroup: string, subGroup1: string, subGroup2: string, name: string, price: ?float}> */
|
|
public array $results = [];
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->form->fill();
|
|
}
|
|
|
|
public function form(Schema $form): Schema
|
|
{
|
|
return $form
|
|
->schema([
|
|
Select::make('supplier')
|
|
->label('Beszállító:')
|
|
->options(function (Get $get) {
|
|
$selectedIds = $this->data['supplier'] ?? [];
|
|
$query = Supplier::query();
|
|
|
|
$productQuery = $this->buildProductQuery($get, exclude: 'supplier');
|
|
if ($productQuery) {
|
|
$query->where(function ($q) use ($productQuery, $selectedIds) {
|
|
$q->whereIn('id', (clone $productQuery)->whereNotNull('supplier_id')->distinct()->pluck('supplier_id'))
|
|
->orWhereIn('id', $selectedIds);
|
|
});
|
|
}
|
|
|
|
return $query->orderBy('name')->pluck('name', 'id');
|
|
})
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->placeholder('Válasszon beszállítót...'),
|
|
Select::make('productGroup')
|
|
->label('Termékcsoport:')
|
|
->options(function (Get $get) {
|
|
$selectedIds = $this->data['productGroup'] ?? [];
|
|
$query = ProductGroup::query();
|
|
|
|
$productQuery = $this->buildProductQuery($get, exclude: 'productGroup');
|
|
if ($productQuery) {
|
|
$query->where(function ($q) use ($productQuery, $selectedIds) {
|
|
$q->whereIn('id', (clone $productQuery)->whereNotNull('product_group_id')->distinct()->pluck('product_group_id'))
|
|
->orWhereIn('id', $selectedIds);
|
|
});
|
|
}
|
|
|
|
return $query->orderBy('name')->pluck('name', 'id');
|
|
})
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->placeholder('Válasszon termékcsoportot...'),
|
|
Select::make('product')
|
|
->label('Termék:')
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->getSearchResultsUsing(function (string $search, Get $get) {
|
|
$query = $this->buildProductQuery($get, exclude: 'product') ?? Product::query();
|
|
$query->where('name', 'like', "%{$search}%");
|
|
|
|
return $query->limit(50)->pluck('name', 'name')->toArray();
|
|
})
|
|
->getOptionLabelsUsing(fn (array $values): array => Product::whereIn('name', $values)
|
|
->pluck('name', 'name')
|
|
->toArray())
|
|
->placeholder('Válasszon terméket...'),
|
|
Select::make('hooreycaName')
|
|
->label('Hooreyca megnevezés :')
|
|
->options(function (Get $get) {
|
|
$selected = $this->data['hooreycaName'] ?? [];
|
|
|
|
DB::statement("SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));");
|
|
$query = ($this->buildProductQuery($get, exclude: 'hooreycaName') ?? Product::query())
|
|
->where('buyerProductName', '!=', '');
|
|
$options = $query->orderBy('buyerProductName')
|
|
->groupBy('buyerProductName')
|
|
->pluck('buyerProductName', 'buyerProductName')
|
|
->toArray();
|
|
DB::statement("SET sql_mode=(SELECT CONCAT(@@sql_mode, ',ONLY_FULL_GROUP_BY'));");
|
|
|
|
foreach ($selected as $name) {
|
|
if (filled($name) && ! array_key_exists($name, $options)) {
|
|
$options[$name] = $name;
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
})
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->placeholder('Válasszon megnevezést...'),
|
|
Select::make('producer')
|
|
->label('Gyártó :')
|
|
->options(function (Get $get) {
|
|
$selectedIds = $this->data['producer'] ?? [];
|
|
$query = Producer::query();
|
|
|
|
$productQuery = $this->buildProductQuery($get, exclude: 'producer');
|
|
if ($productQuery) {
|
|
$query->where(function ($q) use ($productQuery, $selectedIds) {
|
|
$q->whereIn('id', (clone $productQuery)->whereNotNull('producer_id')->distinct()->pluck('producer_id'))
|
|
->orWhereIn('id', $selectedIds);
|
|
});
|
|
}
|
|
|
|
return $query->orderBy('name')->pluck('name', 'id');
|
|
})
|
|
->multiple()
|
|
->searchable()
|
|
->live()
|
|
->placeholder('Válasszon gyártót...'),
|
|
])
|
|
->statePath('data');
|
|
}
|
|
|
|
public function submit(): void
|
|
{
|
|
$this->data = $this->form->getState();
|
|
$this->page = 1;
|
|
$this->runSearch();
|
|
}
|
|
|
|
public function updatedPerPage(): void
|
|
{
|
|
$this->page = 1;
|
|
|
|
if ($this->hasSearched) {
|
|
$this->runSearch();
|
|
}
|
|
}
|
|
|
|
public function goToPage(int $page): void
|
|
{
|
|
$this->page = max(1, min($page, $this->lastPage()));
|
|
$this->runSearch();
|
|
}
|
|
|
|
public function lastPage(): int
|
|
{
|
|
return max(1, (int) ceil($this->totalResults / $this->perPage));
|
|
}
|
|
|
|
public function exportExcel(): BinaryFileResponse
|
|
{
|
|
$products = $this->filteredResultsQuery()->orderBy('name')->get();
|
|
$rows = $this->mapProductsToRows($products);
|
|
|
|
return Excel::download(new CurrentPriceExport($rows), 'aktualis-ar-'.now()->format('Y-m-d_His').'.xlsx');
|
|
}
|
|
|
|
private function runSearch(): void
|
|
{
|
|
$query = $this->filteredResultsQuery();
|
|
|
|
$this->totalResults = (clone $query)->count();
|
|
$this->page = min($this->page, $this->lastPage());
|
|
|
|
$products = $query->orderBy('name')
|
|
->skip(($this->page - 1) * $this->perPage)
|
|
->take($this->perPage)
|
|
->get();
|
|
|
|
$this->results = $this->mapProductsToRows($products)->toArray();
|
|
|
|
$this->hasSearched = true;
|
|
}
|
|
|
|
/**
|
|
* A szűrő űrlap aktuális állapota alapján felépített, még nem lapozott/rendezett Product query.
|
|
*/
|
|
private function filteredResultsQuery(): Builder
|
|
{
|
|
$data = $this->data;
|
|
|
|
$query = Product::query()->with(['Supplier', 'Producer']);
|
|
|
|
if (filled($data['supplier'] ?? [])) {
|
|
$query->whereIn('supplier_id', $data['supplier']);
|
|
}
|
|
|
|
if (filled($data['productGroup'] ?? [])) {
|
|
$query->whereIn('product_group_id', $data['productGroup']);
|
|
}
|
|
|
|
if (filled($data['producer'] ?? [])) {
|
|
$query->whereIn('producer_id', $data['producer']);
|
|
}
|
|
|
|
if (filled($data['product'] ?? [])) {
|
|
$query->whereIn('name', $data['product']);
|
|
}
|
|
|
|
if (filled($data['hooreycaName'] ?? [])) {
|
|
$query->whereIn('buyerProductName', $data['hooreycaName']);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Termékekhez tartozó aktuális ár és termékcsoport-hierarchia feloldása, és a táblázat/export
|
|
* soraivá alakítása. Egyetlen extra lekérdezéssel dolgozza fel a kapott terméklistát (nincs N+1).
|
|
*
|
|
* @return Collection<int, array{supplier: ?string, producer: ?string, mainGroup: string, subGroup1: string, subGroup2: string, name: string, price: ?float}>
|
|
*/
|
|
private function mapProductsToRows(Collection $products): Collection
|
|
{
|
|
$today = now()->toDateString();
|
|
|
|
$currentPrices = DB::table('price_list_prices as plp')
|
|
->join('price_lists as pl', 'pl.id', '=', 'plp.price_list_id')
|
|
->whereIn('plp.product_id', $products->pluck('id'))
|
|
->where('pl.available', '<=', $today)
|
|
->orderBy('pl.available', 'desc')
|
|
->orderBy('pl.id', 'desc')
|
|
->get(['plp.product_id', 'plp.price'])
|
|
->groupBy('product_id')
|
|
->map(fn ($rows) => (float) $rows->first()->price);
|
|
|
|
$groupPaths = $this->getProductGroupPaths($products->pluck('product_group_id'));
|
|
|
|
return $products->map(function (Product $product) use ($currentPrices, $groupPaths) {
|
|
$path = $groupPaths->get($product->product_group_id, []);
|
|
|
|
return [
|
|
'supplier' => $product->Supplier?->name,
|
|
'producer' => $product->Producer?->name,
|
|
'mainGroup' => $path[0] ?? '',
|
|
'subGroup1' => $path[1] ?? '',
|
|
'subGroup2' => $path[2] ?? '',
|
|
'name' => $product->name,
|
|
'price' => $currentPrices->get($product->id),
|
|
];
|
|
})->values();
|
|
}
|
|
|
|
/**
|
|
* A termékcsoport-hierarchia első 3 szintje (Termék Főcsoport / alcsoport 1 / alcsoport 2)
|
|
* termékcsoport id-nként, ugyanaz a logika, mint a legacy Árváltozás statisztikában
|
|
* (ProductGroup::allWithPath() — a technikai gyökér csomópont nélkül).
|
|
*
|
|
* @param \Illuminate\Support\Collection<int, int|null> $productGroupIds
|
|
* @return \Illuminate\Support\Collection<int, array<int, string>>
|
|
*/
|
|
private function getProductGroupPaths($productGroupIds): \Illuminate\Support\Collection
|
|
{
|
|
$ids = $productGroupIds->filter()->unique()->values();
|
|
|
|
if ($ids->isEmpty()) {
|
|
return collect();
|
|
}
|
|
|
|
return ProductGroup::whereIn('id', $ids)
|
|
->with('ancestors')
|
|
->get()
|
|
->mapWithKeys(function (ProductGroup $group) {
|
|
$names = $group->ancestors->pluck('name')->all();
|
|
array_shift($names); // technikai gyökér ("Főcsoportok") elhagyása
|
|
$names[] = $group->name;
|
|
|
|
return [$group->id => $names];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Product query szűrve a szűrő űrlap összes mezőjének aktuális állapota alapján,
|
|
* a $exclude-ban megadott mező kihagyásával (kölcsönös/kereszt-szűréshez).
|
|
* Null-t ad vissza, ha egyik mező sincs kitöltve (nincs mit szűrni).
|
|
*/
|
|
private function buildProductQuery(Get $get, string $exclude): ?Builder
|
|
{
|
|
$supplierIds = $exclude === 'supplier' ? [] : ($get('supplier') ?? []);
|
|
$productGroupIds = $exclude === 'productGroup' ? [] : ($get('productGroup') ?? []);
|
|
$producerIds = $exclude === 'producer' ? [] : ($get('producer') ?? []);
|
|
$productNames = $exclude === 'product' ? [] : ($get('product') ?? []);
|
|
$hooreycaNames = $exclude === 'hooreycaName' ? [] : ($get('hooreycaName') ?? []);
|
|
|
|
if (blank($supplierIds) && blank($productGroupIds) && blank($producerIds) && blank($productNames) && blank($hooreycaNames)) {
|
|
return null;
|
|
}
|
|
|
|
$query = Product::query();
|
|
|
|
if (filled($supplierIds)) {
|
|
$query->whereIn('supplier_id', $supplierIds);
|
|
}
|
|
|
|
if (filled($productGroupIds)) {
|
|
$query->whereIn('product_group_id', $productGroupIds);
|
|
}
|
|
|
|
if (filled($producerIds)) {
|
|
$query->whereIn('producer_id', $producerIds);
|
|
}
|
|
|
|
if (filled($productNames)) {
|
|
$query->whereIn('name', $productNames);
|
|
}
|
|
|
|
if (filled($hooreycaNames)) {
|
|
$query->whereIn('buyerProductName', $hooreycaNames);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
}
|