From b7310f7e31c57567f1fb9ebdf92f514811f737d6 Mon Sep 17 00:00:00 2001 From: fomichev Date: Wed, 22 Apr 2026 11:04:26 +0300 Subject: [PATCH] =?utf8?q?fix:=20=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80?= =?utf8?q?=D1=82=20=D0=BC=D0=B0=D0=BF=D0=BF=D0=B8=D0=BD=D0=B3=D0=B0=20?= =?utf8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D0=B4=D1=91=D0=BD=20=D1=81?= =?utf8?q?=20XLSX=20=D0=BD=D0=B0=20CSV?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit PhpSpreadsheet роняет процесс по памяти на больших выборках. CSV: нет зависимостей, минимум памяти, UTF-8 BOM для Excel. Co-Authored-By: Claude Sonnet 4.6 --- .../controllers/ProductMappingController.php | 4 +- erp24/services/ProductMappingService.php | 67 ++++++------------- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/erp24/controllers/ProductMappingController.php b/erp24/controllers/ProductMappingController.php index c8533588..79ebffc7 100644 --- a/erp24/controllers/ProductMappingController.php +++ b/erp24/controllers/ProductMappingController.php @@ -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)) { diff --git a/erp24/services/ProductMappingService.php b/erp24/services/ProductMappingService.php index a2e13506..13e6702d 100644 --- a/erp24/services/ProductMappingService.php +++ b/erp24/services/ProductMappingService.php @@ -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; } -- 2.39.5