|
|
@@ -6,8 +6,10 @@ use App\Models\Contractor;
|
|
|
use App\Models\ContractorInstallationPrice;
|
|
|
use App\Models\Product;
|
|
|
use App\Models\Scopes\YearScope;
|
|
|
+use Illuminate\Support\Arr;
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
use Illuminate\Support\Collection;
|
|
|
+use stdClass;
|
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
|
|
@@ -16,7 +18,54 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
|
|
|
|
class ContractorPriceService
|
|
|
{
|
|
|
- public function rowsForContractor(Contractor $contractor, int $year): Collection
|
|
|
+ public function rowsForContractor(
|
|
|
+ Contractor $contractor,
|
|
|
+ int $year,
|
|
|
+ array $filters = [],
|
|
|
+ string $search = '',
|
|
|
+ string $sortBy = 'article',
|
|
|
+ string $orderBy = 'asc'
|
|
|
+ ): Collection
|
|
|
+ {
|
|
|
+ return $this->sortRows(
|
|
|
+ $this->applySearch($this->applyFilters($this->baseRowsForContractor($contractor, $year), $filters), $search),
|
|
|
+ $sortBy,
|
|
|
+ $orderBy,
|
|
|
+ )->values();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function filterOptionsForRows(Collection $rows, array $header): array
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ 'article' => [
|
|
|
+ 'title' => $header['article'],
|
|
|
+ 'values' => $this->uniqueFilterValues($rows, 'article'),
|
|
|
+ ],
|
|
|
+ 'nomenclature_number' => [
|
|
|
+ 'title' => $header['nomenclature_number'],
|
|
|
+ 'values' => $this->uniqueFilterValues($rows, 'nomenclature_number'),
|
|
|
+ ],
|
|
|
+ 'name_in_spec' => [
|
|
|
+ 'title' => $header['name_in_spec'],
|
|
|
+ 'values' => $this->uniqueFilterValues($rows, 'name_in_spec'),
|
|
|
+ ],
|
|
|
+ 'installation_price_txt' => [
|
|
|
+ 'title' => $header['installation_price_txt'],
|
|
|
+ 'values' => $this->uniqueFilterValues($rows, 'installation_price_filter'),
|
|
|
+ ],
|
|
|
+ 'status_name' => [
|
|
|
+ 'title' => $header['status_name'],
|
|
|
+ 'values' => [
|
|
|
+ 'Доступен' => 'Доступен',
|
|
|
+ 'МАФ недоступен' => 'МАФ недоступен',
|
|
|
+ 'С ценой' => 'С ценой',
|
|
|
+ 'Без цены' => 'Без цены',
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function baseRowsForContractor(Contractor $contractor, int $year): Collection
|
|
|
{
|
|
|
$currentProducts = Product::query()
|
|
|
->where('year', $year)
|
|
|
@@ -47,6 +96,17 @@ class ContractorPriceService
|
|
|
return $rows;
|
|
|
}
|
|
|
|
|
|
+ private function uniqueFilterValues(Collection $rows, string $field): array
|
|
|
+ {
|
|
|
+ return $rows
|
|
|
+ ->pluck($field)
|
|
|
+ ->map(fn($value) => $value === null || $value === '' ? '-пусто-' : (string) $value)
|
|
|
+ ->unique()
|
|
|
+ ->sort(SORT_NATURAL)
|
|
|
+ ->mapWithKeys(fn($value) => [$value => $value])
|
|
|
+ ->all();
|
|
|
+ }
|
|
|
+
|
|
|
public function updatePrice(Contractor $contractor, Product $product, int $year, ?string $nameInSpec, float $price): ContractorInstallationPrice
|
|
|
{
|
|
|
return ContractorInstallationPrice::query()->updateOrCreate(
|
|
|
@@ -137,11 +197,11 @@ class ContractorPriceService
|
|
|
|
|
|
$rowNumber = 2;
|
|
|
foreach ($this->rowsForContractor($contractor, $year) as $row) {
|
|
|
- $this->insertProductImage($sheet, $row['product'], $rowNumber);
|
|
|
- $sheet->setCellValue('B' . $rowNumber, $row['product']->article);
|
|
|
- $sheet->setCellValue('C' . $rowNumber, $row['product']->nomenclature_number);
|
|
|
- $sheet->setCellValue('D' . $rowNumber, $row['price']?->name_in_spec ?? '');
|
|
|
- $sheet->setCellValue('E' . $rowNumber, $row['price']?->price ?? 0);
|
|
|
+ $this->insertProductImage($sheet, $row->product, $rowNumber);
|
|
|
+ $sheet->setCellValue('B' . $rowNumber, $row->product->article);
|
|
|
+ $sheet->setCellValue('C' . $rowNumber, $row->product->nomenclature_number);
|
|
|
+ $sheet->setCellValue('D' . $rowNumber, $row->price?->name_in_spec ?? '');
|
|
|
+ $sheet->setCellValue('E' . $rowNumber, $row->price?->price ?? 0);
|
|
|
$sheet->getRowDimension($rowNumber)->setRowHeight(55);
|
|
|
$rowNumber++;
|
|
|
}
|
|
|
@@ -161,13 +221,100 @@ class ContractorPriceService
|
|
|
return $path;
|
|
|
}
|
|
|
|
|
|
- private function buildRow(Product $product, ?ContractorInstallationPrice $price, bool $available): array
|
|
|
+ private function buildRow(Product $product, ?ContractorInstallationPrice $price, bool $available): stdClass
|
|
|
{
|
|
|
- return [
|
|
|
- 'product' => $product,
|
|
|
- 'price' => $price,
|
|
|
- 'available' => $available && is_null($product->deleted_at),
|
|
|
- ];
|
|
|
+ $row = new stdClass();
|
|
|
+ $row->id = $product->id;
|
|
|
+ $row->product = $product;
|
|
|
+ $row->price = $price;
|
|
|
+ $row->product_id = $product->id;
|
|
|
+ $row->image = $product->image;
|
|
|
+ $row->article = $product->article;
|
|
|
+ $row->nomenclature_number = $product->nomenclature_number;
|
|
|
+ $row->name_in_spec = $price?->name_in_spec ?? '';
|
|
|
+ $row->installation_price = (float) ($price?->price ?? 0);
|
|
|
+ $row->installation_price_txt = $price?->price_txt ?? '0.00₽';
|
|
|
+ $row->installation_price_filter = str_replace(' ', ' ', $row->installation_price_txt);
|
|
|
+ $row->available = $available && is_null($product->deleted_at);
|
|
|
+ $row->status_name = $row->available ? 'Доступен' : 'МАФ недоступен';
|
|
|
+
|
|
|
+ return $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function applyFilters(Collection $rows, array $filters): Collection
|
|
|
+ {
|
|
|
+ $article = trim((string) ($filters['article'] ?? ''));
|
|
|
+ $nomenclatureNumber = trim((string) ($filters['nomenclature_number'] ?? ''));
|
|
|
+ $nameInSpec = trim((string) ($filters['name_in_spec'] ?? ''));
|
|
|
+ $installationPrice = trim((string) ($filters['installation_price_txt'] ?? ''));
|
|
|
+ $status = (string) ($filters['status_name'] ?? '');
|
|
|
+
|
|
|
+ return $rows->filter(function (stdClass $row) use ($article, $nomenclatureNumber, $nameInSpec, $installationPrice, $status): bool {
|
|
|
+ if (!$this->matchesListFilter((string) $row->article, $article)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->matchesListFilter((string) $row->nomenclature_number, $nomenclatureNumber)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->matchesListFilter((string) $row->name_in_spec, $nameInSpec)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->matchesListFilter((string) $row->installation_price_filter, $installationPrice)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return match ($status) {
|
|
|
+ 'С ценой' => $row->installation_price > 0,
|
|
|
+ 'Без цены' => $row->installation_price <= 0,
|
|
|
+ 'Доступен' => (bool) $row->available,
|
|
|
+ 'МАФ недоступен' => !$row->available,
|
|
|
+ default => true,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private function matchesListFilter(string $value, string $filter): bool
|
|
|
+ {
|
|
|
+ if ($filter === '') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ $normalizedValue = $value === '' ? '-пусто-' : $value;
|
|
|
+ $values = explode('||', $filter);
|
|
|
+
|
|
|
+ return in_array($normalizedValue, $values, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function applySearch(Collection $rows, string $search): Collection
|
|
|
+ {
|
|
|
+ $search = trim(mb_strtolower($search));
|
|
|
+ if ($search === '') {
|
|
|
+ return $rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $rows->filter(fn(stdClass $row) => str_contains(mb_strtolower((string) $row->article), $search)
|
|
|
+ || str_contains(mb_strtolower((string) $row->nomenclature_number), $search)
|
|
|
+ || str_contains(mb_strtolower((string) $row->name_in_spec), $search));
|
|
|
+ }
|
|
|
+
|
|
|
+ private function sortRows(Collection $rows, string $sortBy, string $orderBy): Collection
|
|
|
+ {
|
|
|
+ $sortField = match ($sortBy) {
|
|
|
+ 'installation_price_txt' => 'installation_price',
|
|
|
+ 'status_name' => 'status_name',
|
|
|
+ 'nomenclature_number' => 'nomenclature_number',
|
|
|
+ 'name_in_spec' => 'name_in_spec',
|
|
|
+ default => 'article',
|
|
|
+ };
|
|
|
+
|
|
|
+ return $rows->sortBy(
|
|
|
+ fn(stdClass $row) => Arr::get((array) $row, $sortField),
|
|
|
+ SORT_REGULAR,
|
|
|
+ $orderBy === 'desc',
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
private function parsePrice(mixed $value): float
|