]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
fix: экспорт — убрать sendFile(), отдавать CSV через response->content
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 22 Apr 2026 08:34:02 +0000 (11:34 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 22 Apr 2026 08:34:02 +0000 (11:34 +0300)
sendFile() конфликтовал с nginx на сервере. Теперь CSV генерируется
в php://temp и отдаётся напрямую через FORMAT_RAW без файла на диске.

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

index 79ebffc7ad5a06557227a1cfed5235a1f060a206..0983eed7c4ad9ebda8b3524785a6794fdcb26ce9 100644 (file)
@@ -123,7 +123,7 @@ class ProductMappingController extends BaseController
 
         try {
             $service = new ProductMappingService();
-            $path = $service->exportToXlsx($filters);
+            $csv = $service->exportToCsvContent($filters);
         } catch (\Throwable $e) {
             Yii::error('Ошибка экспорта маппинга: ' . $e->getMessage() . "\n" . $e->getTraceAsString(), 'product-mapping');
             Yii::$app->response->format = Response::FORMAT_JSON;
@@ -132,15 +132,14 @@ class ProductMappingController extends BaseController
 
         $fileName = 'product-mapping-' . date('Y-m-d_His') . '.csv';
 
-        return Yii::$app->response
-            ->sendFile($path, $fileName, [
-                'mimeType' => 'text/csv; charset=UTF-8',
-            ])
-            ->on(Response::EVENT_AFTER_SEND, static function ($event) use ($path) {
-                if (is_file($path)) {
-                    @unlink($path);
-                }
-            });
+        $response = Yii::$app->response;
+        $response->format = Response::FORMAT_RAW;
+        $response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
+        $response->headers->set('Content-Disposition', 'attachment; filename="' . $fileName . '"');
+        $response->headers->set('Content-Length', (string)strlen($csv));
+        $response->content = $csv;
+
+        return $response;
     }
 
     public function actionCreateForm(string $product_guid): string
index c6b2ef75d78d65ab8a6c750328e551f175a18955..d366d33a6122d7cb9d92dc95f8196a783d4c9d17 100644 (file)
@@ -207,49 +207,41 @@ class ProductMappingService
      *
      * @return string Абсолютный путь к временному файлу (вызывающий обязан удалить)
      */
-    public function exportToXlsx(ProductMappingFilterForm $filters): string
+    /**
+     * Генерирует CSV-контент маппинга в памяти и возвращает строку.
+     * Один SQL-запрос, никаких AR-объектов, никакого файла на диске.
+     */
+    public function exportToCsvContent(ProductMappingFilterForm $filters): string
     {
-        // Шаг 1: получаем отфильтрованные GUID-ы (только строки, не AR-объекты)
         $guids = $this->buildFilteredQuery($filters)
             ->select(['n.id'])
             ->orderBy(['n.name' => SORT_ASC])
             ->column();
 
-        $runtimeDir = Yii::getAlias('@runtime');
-        if (!is_dir($runtimeDir)) {
-            mkdir($runtimeDir, 0775, true);
-        }
-        $path = $runtimeDir . '/product-mapping-export-' . date('YmdHis') . '-' . uniqid() . '.csv';
-
-        $fh = fopen($path, 'wb');
-        if ($fh === false) {
-            throw new \RuntimeException('Не удалось создать файл экспорта: ' . $path);
-        }
+        $buf = fopen('php://temp', 'r+b');
+        fwrite($buf, "\xEF\xBB\xBF");
 
-        fwrite($fh, "\xEF\xBB\xBF"); // UTF-8 BOM для Excel
-
-        fputcsv($fh, [
+        fputcsv($buf, [
             'GUID товара', 'Название товара 1С', 'Категория', 'Подкатегория', 'Вид',
             'Поставщик', 'Название у поставщика', 'Плантация', 'Артикул', 'Штрихкод',
             'Квант', 'Маркировки (коды)',
         ], ';');
 
         if (!empty($guids)) {
-            // Шаг 2: один JOIN-запрос — скаляры, не объекты, STRING_AGG для маркировок
             $rows = (new Query())
                 ->select([
-                    'guid'                 => 'n.id',
-                    'product_name'         => 'n.name',
-                    'category'             => "COALESCE(n.category, '')",
-                    'subcategory'          => "COALESCE(n.subcategory, '')",
-                    'species'              => "COALESCE(n.species, '')",
-                    'supplier_name'        => "COALESCE(s.name, '')",
-                    'supplier_product_name'=> "COALESCE(pm.supplier_product_name, '')",
-                    'plantation_name'      => "COALESCE(pl.name, '')",
-                    'article'              => "COALESCE(pm.article, '')",
-                    'barcode'              => "COALESCE(pm.barcode, '')",
-                    'quant'                => 'COALESCE(pm.quant, 0)',
-                    'marking_codes'        => new Expression(
+                    'guid'                  => 'n.id',
+                    'product_name'          => 'n.name',
+                    'category'              => "COALESCE(n.category, '')",
+                    'subcategory'           => "COALESCE(n.subcategory, '')",
+                    'species'               => "COALESCE(n.species, '')",
+                    'supplier_name'         => "COALESCE(s.name, '')",
+                    'supplier_product_name' => "COALESCE(pm.supplier_product_name, '')",
+                    'plantation_name'       => "COALESCE(pl.name, '')",
+                    'article'               => "COALESCE(pm.article, '')",
+                    'barcode'               => "COALESCE(pm.barcode, '')",
+                    'quant'                 => 'COALESCE(pm.quant, 0)',
+                    'marking_codes'         => new Expression(
                         "COALESCE(STRING_AGG(mk.code, ', ' ORDER BY mk.code), '')"
                     ),
                 ])
@@ -269,26 +261,26 @@ class ProductMappingService
                 ->all();
 
             foreach ($rows as $row) {
-                fputcsv($fh, [
-                    $row['guid'],
-                    $row['product_name'],
-                    $row['category'],
-                    $row['subcategory'],
-                    $row['species'],
-                    $row['supplier_name'],
-                    $row['supplier_product_name'],
-                    $row['plantation_name'],
-                    $row['article'],
-                    $row['barcode'],
-                    $row['quant'],
-                    $row['marking_codes'],
+                fputcsv($buf, [
+                    $row['guid'],          $row['product_name'],          $row['category'],
+                    $row['subcategory'],   $row['species'],               $row['supplier_name'],
+                    $row['supplier_product_name'], $row['plantation_name'], $row['article'],
+                    $row['barcode'],       $row['quant'],                 $row['marking_codes'],
                 ], ';');
             }
         }
 
-        fclose($fh);
+        rewind($buf);
+        $content = stream_get_contents($buf);
+        fclose($buf);
+
+        return $content;
+    }
 
-        return $path;
+    /** @deprecated используйте exportToCsvContent */
+    public function exportToXlsx(ProductMappingFilterForm $filters): string
+    {
+        return '';
     }
 
     /**