Bladeren bron

- refactored catalog import to new style

Alexander Musikhin 1 dag geleden
bovenliggende
commit
909992b9ea

+ 2 - 1
app/Http/Controllers/ImportController.php

@@ -56,7 +56,7 @@ class ImportController extends Controller
     {
         // validate data
         $request->validate([
-            'type'        => 'required|in:orders,reclamations,mafs',
+            'type'        => 'required|in:orders,reclamations,mafs,catalog',
             'import_file' => 'required|file',
         ]);
 
@@ -66,6 +66,7 @@ class ImportController extends Controller
 
         $import = Import::query()->create([
             'type' => $request->type,
+            'year' => ($request->type === 'catalog') ? year() : null,
             'status' => 'new',
             'filename' => $path,
         ]);

+ 0 - 23
app/Http/Controllers/ProductController.php

@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
 
 use App\Http\Requests\StoreProductRequest;
 use App\Jobs\Export\ExportCatalog;
-use App\Jobs\Import\ImportCatalog;
 use App\Models\File;
 use App\Models\Product;
 use App\Services\FileService;
@@ -12,7 +11,6 @@ use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Str;
 
 class ProductController extends Controller
 {
@@ -113,27 +111,6 @@ class ProductController extends Controller
         return redirect()->route('catalog.index', session('gp_products'));
     }
 
-    /**
-     * @param Request $request
-     * @return RedirectResponse
-     */
-    public function import(Request $request)
-    {
-        // validate data
-        $request->validate([
-            'import_file' => 'file',
-        ]);
-
-        // load and save file
-        $path = Str::random(2) . '/' . Str::uuid() . '.' .$request->file('import_file')->getClientOriginalExtension();
-        Storage::disk('upload')->put($path, $request->file('import_file')->getContent());
-
-        // dispatch job
-        ImportCatalog::dispatch($path, year(), $request->user()->id);
-        Log::info('ImportCatalog job created!');
-        return redirect()->route('catalog.index', session('gp_products'))->with(['success' => 'Задача импорта успешно создана!']);
-    }
-
     public function export(Request $request)
     {
         $request->validate([

+ 0 - 39
app/Jobs/Import/ImportCatalog.php

@@ -1,39 +0,0 @@
-<?php
-
-namespace App\Jobs\Import;
-
-use App\Events\SendWebSocketMessageEvent;
-use App\Services\ImportService;
-use Exception;
-use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Foundation\Queue\Queueable;
-use Illuminate\Support\Facades\Log;
-
-class ImportCatalog implements ShouldQueue, ShouldBeUniqueUntilProcessing
-{
-    use Queueable;
-
-    /**
-     * Create a new job instance.
-     */
-    public function __construct(private readonly string $path, private readonly int $year, private readonly int $userId)
-    {
-        //
-    }
-
-    /**
-     * @return void
-     */
-    public function handle(): void
-    {
-        try {
-            (new ImportService)->handle($this->path, $this->year);
-            Log::info('ImportCatalog job done!');
-            event(new SendWebSocketMessageEvent('Импорт завершён!', $this->userId, ['year' => $this->year, 'path' => $this->path]));
-        } catch (Exception $e) {
-            Log::info('ImportCatalog job failed! ERROR: ' . $e->getMessage());
-            event(new SendWebSocketMessageEvent('Ошибка импорта! ' . $e->getMessage(), $this->userId, ['error' => $e->getMessage()]));
-        }
-    }
-}

+ 4 - 0
app/Jobs/Import/ImportJob.php

@@ -4,6 +4,7 @@ namespace App\Jobs\Import;
 
 use App\Events\SendWebSocketMessageEvent;
 use App\Models\Import;
+use App\Services\ImportCatalogService;
 use App\Services\ImportMafsService;
 use App\Services\ImportOrdersService;
 use App\Services\ImportReclamationsService;
@@ -41,6 +42,9 @@ class ImportJob implements ShouldQueue
                 case 'reclamations':
                     (new ImportReclamationsService($this->import))->handle();
                     break;
+                case 'catalog':
+                    (new ImportCatalogService($this->import, $this->import->year))->handle();
+                    break;
             }
             Log::info('Import ' . $this->import->type. ' job done!');
             event(new SendWebSocketMessageEvent('Импорт завершён!', $this->userId, ['path' => $this->import->filename, 'type' => $this->import->type]));

+ 1 - 0
app/Models/Import.php

@@ -10,6 +10,7 @@ class Import extends Model
 
     protected $fillable = [
         'type',
+        'year',
         'filename',
         'status',
         'result',

+ 133 - 0
app/Services/ImportCatalogService.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace App\Services;
+
+use App\Helpers\DateHelper;
+use App\Models\Import;
+use App\Models\Product;
+
+class ImportCatalogService extends ImportBaseService
+{
+    const HEADERS = [
+        'Фото'                          => 'photo',
+        'Артикул образца'               => 'article',
+        'Наименование по ТЗ'            => 'name_tz',
+        'Тип по ТЗ'                     => 'type_tz',
+        '№ по номенкл.'                 => 'nomenclature_number',
+        'Габаритные размеры'            => 'sizes',
+        'Производитель'                 => 'manufacturer',
+        'ед. изм.'                      => 'unit',
+        'Тип оборудования'              => 'type',
+        'Цена поставки'                 => 'product_price',
+        'Цена установки'                => 'installation_price',
+        'Итого цена'                    => 'total_price',
+        'Наименование производителя'    => 'manufacturer_name',
+        'Примечание'                    => 'note',
+        'Наименование по паспорту'      => 'passport_name',
+        'Наименование в ведомости'      => 'statement_name',
+        'Срок службы'                   => 'service_life',
+        'Номер сертификата'             => 'certificate_number',
+        'Дата сертификата'              => 'certificate_date',
+        'Орган сертификации'            => 'certificate_issuer',
+        'Вид сертификата'               => 'certificate_type',
+        'Вес'                           => 'weight',
+        'Объем'                         => 'volume',
+        'Мест'                          => 'places',
+    ];
+
+    private int $year;
+
+    public function __construct(Import $import, int $year)
+    {
+        parent::__construct($import);
+        $this->headers = self::HEADERS;
+        $this->year = $year;
+    }
+
+    public function handle(): bool
+    {
+        if (!$this->prepare()) {
+            return false;
+        }
+
+        $strNumber = 0;
+        $result = [
+            'productsCreated' => 0,
+            'productsUpdated' => 0,
+        ];
+
+        foreach ($this->rowIterator as $row) {
+            $strNumber++;
+            $r = $this->rowToArray($row);
+
+            if ($strNumber === 1) {
+                echo $this->import->log('Skip headers Row: ' . $strNumber);
+                continue;
+            }
+
+            // Пропускаем пустые строки
+            if (empty($r['nomenclature_number'])) {
+                continue;
+            }
+
+            try {
+                $logMessage = "Row $strNumber: " . $r['nomenclature_number'] . ' - ' . $r['name_tz'] . '. ';
+
+                $certDate = (int) $r['certificate_date'];
+
+                $existing = Product::query()
+                    ->withoutGlobalScopes()
+                    ->where('year', $this->year)
+                    ->where('nomenclature_number', $r['nomenclature_number'])
+                    ->first();
+
+                $data = [
+                    'article'               => (string) $r['article'],
+                    'name_tz'               => (string) $r['name_tz'],
+                    'type_tz'               => (string) $r['type_tz'],
+                    'sizes'                 => (string) $r['sizes'],
+                    'manufacturer'          => (string) $r['manufacturer'],
+                    'unit'                  => (string) $r['unit'],
+                    'type'                  => (string) $r['type'],
+                    'product_price'         => (float) $r['product_price'],
+                    'installation_price'    => (float) $r['installation_price'],
+                    'total_price'           => (float) $r['total_price'],
+                    'manufacturer_name'     => (string) $r['manufacturer_name'],
+                    'note'                  => (string) $r['note'],
+                    'passport_name'         => (string) $r['passport_name'],
+                    'statement_name'        => (string) $r['statement_name'],
+                    'service_life'          => (int) $r['service_life'],
+                    'certificate_number'    => (string) $r['certificate_number'],
+                    'certificate_date'      => ($certDate > 0) ? DateHelper::excelDateToISODate($certDate) : null,
+                    'certificate_issuer'    => (string) $r['certificate_issuer'],
+                    'certificate_type'      => (string) $r['certificate_type'],
+                    'weight'                => (float) $r['weight'],
+                    'volume'                => (float) $r['volume'],
+                    'places'                => (int) $r['places'],
+                ];
+
+                if ($existing) {
+                    $existing->update($data);
+                    $logMessage .= 'Updated product ID: ' . $existing->id;
+                    $result['productsUpdated']++;
+                } else {
+                    $data['year'] = $this->year;
+                    $data['nomenclature_number'] = $r['nomenclature_number'];
+                    $product = Product::query()->create($data);
+                    $logMessage .= 'Created product ID: ' . $product->id;
+                    $result['productsCreated']++;
+                }
+
+                echo $this->import->log($logMessage);
+            } catch (\Exception $e) {
+                echo $this->import->log("Row $strNumber: " . $e->getMessage(), 'WARNING');
+            }
+        }
+
+        echo $this->import->log(print_r($result, true));
+        $this->import->status = 'DONE';
+        $this->import->save();
+
+        return true;
+    }
+}

+ 0 - 129
app/Services/ImportService.php

@@ -1,129 +0,0 @@
-<?php
-
-namespace App\Services;
-
-use App\Helpers\DateHelper;
-use App\Models\Product;
-use Exception;
-use Illuminate\Support\Facades\Storage;
-use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
-
-class ImportService
-{
-    const HEADERS_TO_FIELDS = [
-        "Фото" => '',
-        "Артикул образца"               => 'article',
-        "Наименование по ТЗ"            => 'name_tz',
-        "Тип по ТЗ"                     => 'type_tz',
-        "№ по номенкл."                 => 'nomenclature_number',
-        "Габаритные размеры"            => 'sizes',
-        "Производитель"                 => 'manufacturer',
-        "ед. изм."                      => 'unit',
-        "Тип оборудования"              => 'type',
-        "Цена поставки"                 => 'product_price',
-        "Цена установки"                => 'installation_price',
-        "Итого цена"                    => 'total_price',
-        "Наименование производителя"    => 'manufacturer_name',
-        "Примечание"                    => 'note',
-        "Наименование по паспорту"      => 'passport_name',
-        "Наименование в ведомости"      => 'statement_name',
-        "Срок службы"                   => 'service_life',
-        "Номер сертификата"             => 'certificate_number',
-        "Дата сертификата"              => 'certificate_date',
-        "Орган сертификации"            => 'certificate_issuer',
-        "Вид сертификата"               => 'certificate_type',
-        "Вес"                           => 'weight',
-        "Объем"                         => 'volume',
-        "Мест"                          => 'places',
-    ];
-
-
-    /**
-     * @param string $path
-     * @param int $year
-     * @return void
-     * @throws Exception
-     */
-    public function handle(string $path, int $year): void
-    {
-        $path = Storage::disk('upload')->path($path);
-
-        $reader = new Xlsx();
-        $spreadsheet = $reader->load($path);
-        $sheet = $spreadsheet->getActiveSheet();
-
-        $rowIterator = $sheet->getRowIterator();
-
-        $headers = $this->rowToArray($rowIterator->current());
-
-
-
-        if($this->checkHeaders($headers)) {
-            foreach ($rowIterator as $row){
-                $record = $this->rowToArray($row);
-                if($record[0] === 'Фото') continue;
-                if(!isset($record[4])) continue;
-                $certDate = (int) $record[18];
-                Product::query()
-                    ->withoutGlobalScopes()
-                    ->updateOrCreate(['year' => $year, 'nomenclature_number' => $record[4]],
-                    [
-                        'article'               => (string) $record[1],
-                        'name_tz'               => (string) $record[2],
-                        'type_tz'               => (string) $record[3],
-                        'sizes'                 => (string) $record[5],
-                        'manufacturer'          => (string) $record[6],
-                        'unit'                  => (string) $record[7],
-                        'type'                  => (string) $record[8],
-                        'product_price'         => (float) $record[9],
-                        'installation_price'    => (float) $record[10],
-                        'total_price'           => (float) $record[11],
-                        'manufacturer_name'     => (string) $record[12],
-                        'note'                  => (string) $record[13],
-                        'passport_name'         => (string) $record[14],
-                        'statement_name'        => (string) $record[15],
-                        'service_life'          => (int) $record[16],
-                        'certificate_number'    => (string) $record[17],
-                        'certificate_date'      => ($certDate > 0) ? DateHelper::excelDateToISODate($certDate) : null,
-                        'certificate_issuer'    => (string) $record[19],
-                        'certificate_type'      => (string) $record[20],
-                        'weight'                => (float) $record[21],
-                        'volume'                => (float) $record[22],
-                        'places'                => (int) $record[23],
-                    ]);
-            }
-        } else {
-            throw new Exception('Ошибка заголовков файла!');
-        }
-    }
-
-    protected function rowToArray($row): array
-    {
-        $cellIterator = $row->getCellIterator();
-        $cellIterator->setIterateOnlyExistingCells(FALSE); // This loops through all cells, even if a cell value is not set.
-        $row_content = [];
-        foreach ($cellIterator as $cell) {
-            $row_content[] = $cell->getValue();
-        }
-
-        return $row_content;
-    }
-
-    /**
-     * @param array $headers
-     * @return bool
-     */
-    protected function checkHeaders(array $headers): bool
-    {
-        return $this->getHeaders() == $headers;
-    }
-
-
-    /**
-     * @return array
-     */
-    protected function getHeaders(): array
-    {
-        return array_keys(self::HEADERS_TO_FIELDS);
-    }
-}

+ 28 - 0
database/migrations/2026_01_23_120000_add_year_to_imports_table.php

@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('imports', function (Blueprint $table) {
+            $table->integer('year')->nullable()->after('type');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('imports', function (Blueprint $table) {
+            $table->dropColumn('year');
+        });
+    }
+};

+ 2 - 1
resources/views/catalog/index.blade.php

@@ -41,8 +41,9 @@
                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
                 </div>
                 <div class="modal-body">
-                    <form action="{{ route('catalog.import') }}" method="post" enctype="multipart/form-data">
+                    <form action="{{ route('import.create') }}" method="post" enctype="multipart/form-data">
                         @csrf
+                        <input type="hidden" name="type" value="catalog">
                         @include('partials.input', ['title' => 'XLSX файл', 'name' => 'import_file', 'type' => 'file', 'required' => true])
                         @include('partials.submit', ['name' => 'Импорт'])
                     </form>

+ 1 - 1
resources/views/import/index.blade.php

@@ -36,7 +36,7 @@
                 <div class="modal-body">
                     <form action="{{ route('import.create') }}" method="post" enctype="multipart/form-data">
                         @csrf
-                        @include('partials.select', ['title' => 'Вкладка', 'name' => 'type', 'options' => ['orders' => 'Площадки', 'reclamations' => 'Рекламации', 'mafs' => 'МАФы']])
+                        @include('partials.select', ['title' => 'Вкладка', 'name' => 'type', 'options' => ['orders' => 'Площадки', 'reclamations' => 'Рекламации', 'mafs' => 'МАФы', 'catalog' => 'Каталог']])
                         @include('partials.input', ['title' => 'XLSX файл', 'name' => 'import_file', 'type' => 'file', 'required' => true])
                         @include('partials.submit', ['name' => 'Импорт'])
                     </form>

+ 0 - 1
routes/web.php

@@ -146,7 +146,6 @@ Route::middleware('auth:web')->group(function () {
         Route::post('catalog/{product}', [ProductController::class, 'update'])->name('catalog.update');
         Route::delete('catalog/{product}', [ProductController::class, 'delete'])->name('catalog.delete');
 
-        Route::post('catalog-import', [ProductController::class, 'import'])->name('catalog.import');
         Route::post('catalog-export', [ProductController::class, 'export'])->name('catalog.export');
 
         Route::post('mafs-import', [ProductSKUController::class, 'importMaf'])->name('mafs.import');