]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
fix: экспорт маппинга переведён с XLSX на CSV
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 22 Apr 2026 08:04:26 +0000 (11:04 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 22 Apr 2026 08:04:26 +0000 (11:04 +0300)
PhpSpreadsheet роняет процесс по памяти на больших выборках.
CSV: нет зависимостей, минимум памяти, UTF-8 BOM для Excel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
erp24/controllers/ProductMappingController.php
erp24/services/ProductMappingService.php

index c8533588b148b18931200b565afb3aec92a6bc3a..79ebffc7ad5a06557227a1cfed5235a1f060a206 100644 (file)
@@ -130,11 +130,11 @@ class ProductMappingController extends BaseController
             return $this->asJson(['success' => false, 'message' => $e->getMessage()]);
         }
 
-        $fileName = 'product-mapping-' . date('Y-m-d_His') . '.xlsx';
+        $fileName = 'product-mapping-' . date('Y-m-d_His') . '.csv';
 
         return Yii::$app->response
             ->sendFile($path, $fileName, [
-                'mimeType' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+                'mimeType' => 'text/csv; charset=UTF-8',
             ])
             ->on(Response::EVENT_AFTER_SEND, static function ($event) use ($path) {
                 if (is_file($path)) {
index a2e13506635ec4fecb0eba99968e688b716a99bc..13e6702d129fea70603cdca04b7c84b2c15e49de 100644 (file)
@@ -4,10 +4,6 @@ declare(strict_types=1);
 
 namespace yii_app\services;
 
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Style\Alignment;
-use PhpOffice\PhpSpreadsheet\Style\Fill;
-use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 use Yii;
 use yii\data\Pagination;
 use yii\db\Expression;
@@ -203,16 +199,16 @@ class ProductMappingService
     }
 
     /**
-     * Экспорт маппинга в .xlsx с учётом фильтров.
+     * Экспорт маппинга в CSV с учётом фильтров.
      *
      * Формат: одна строка на пару (товар, маппинг).
      * Товары без маппингов — отдельной строкой с пустыми полями маппинга.
+     * Кодировка UTF-8 BOM (Excel корректно открывает без настроек импорта).
      *
      * @return string Абсолютный путь к временному файлу (вызывающий обязан удалить)
      */
     public function exportToXlsx(ProductMappingFilterForm $filters): string
     {
-        // Все товары по фильтрам (без пагинации)
         $query = $this->buildFilteredQuery($filters)->orderBy(['n.name' => SORT_ASC]);
 
         /** @var Products1cNomenclature[] $products */
@@ -221,11 +217,17 @@ class ProductMappingService
             array_map(static fn($p) => $p->id, $products)
         );
 
-        $spreadsheet = new Spreadsheet();
-        $sheet = $spreadsheet->getActiveSheet();
-        $sheet->setTitle('Маппинг товаров');
+        $runtimeDir = Yii::getAlias('@runtime');
+        if (!is_dir($runtimeDir)) {
+            mkdir($runtimeDir, 0775, true);
+        }
+        $path = $runtimeDir . '/product-mapping-export-' . date('YmdHis') . '-' . uniqid() . '.csv';
 
-        $headers = [
+        $fh = fopen($path, 'w');
+        // BOM для корректного открытия в Excel
+        fwrite($fh, "\xEF\xBB\xBF");
+
+        fputcsv($fh, [
             'GUID товара',
             'Название товара 1С',
             'Категория',
@@ -238,41 +240,26 @@ class ProductMappingService
             'Штрихкод',
             'Квант',
             'Маркировки (коды)',
-        ];
-        $sheet->fromArray($headers, null, 'A1');
-
-        // Стиль заголовков: bold + серый фон
-        $lastCol = $sheet->getHighestColumn();
-        $sheet->getStyle("A1:{$lastCol}1")->getFont()->setBold(true);
-        $sheet->getStyle("A1:{$lastCol}1")->getFill()
-            ->setFillType(Fill::FILL_SOLID)
-            ->getStartColor()->setRGB('E9ECEF');
-        $sheet->getStyle("A1:{$lastCol}1")->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
-        $sheet->freezePane('A2');
-
-        $row = 2;
+        ], ';');
+
         foreach ($products as $product) {
             $mappings = $mappingsByGuid[$product->id] ?? [];
 
             if (empty($mappings)) {
-                $sheet->fromArray([
+                fputcsv($fh, [
                     $product->id,
                     $product->name,
                     $product->category ?? '',
                     $product->subcategory ?? '',
                     $product->species ?? '',
                     '', '', '', '', '', '', '',
-                ], null, "A{$row}");
-                $row++;
+                ], ';');
                 continue;
             }
 
             foreach ($mappings as $mapping) {
-                $markingCodes = [];
-                foreach ($mapping->markings as $m) {
-                    $markingCodes[] = $m->code;
-                }
-                $sheet->fromArray([
+                $markingCodes = array_map(static fn($m) => $m->code, $mapping->markings);
+                fputcsv($fh, [
                     $product->id,
                     $product->name,
                     $product->category ?? '',
@@ -285,25 +272,11 @@ class ProductMappingService
                     $mapping->barcode ?? '',
                     (int)$mapping->quant,
                     implode(', ', $markingCodes),
-                ], null, "A{$row}");
-                $row++;
+                ], ';');
             }
         }
 
-        // Авто-ширина колонок
-        foreach (range('A', $lastCol) as $col) {
-            $sheet->getColumnDimension($col)->setAutoSize(true);
-        }
-
-        $runtimeDir = Yii::getAlias('@runtime');
-        if (!is_dir($runtimeDir)) {
-            mkdir($runtimeDir, 0775, true);
-        }
-        $path = $runtimeDir . '/product-mapping-export-' . date('YmdHis') . '-' . uniqid() . '.xlsx';
-
-        (new Xlsx($spreadsheet))->save($path);
-        $spreadsheet->disconnectWorksheets();
-        unset($spreadsheet);
+        fclose($fh);
 
         return $path;
     }