From: fomichev Date: Wed, 22 Apr 2026 08:04:26 +0000 (+0300) Subject: fix: экспорт маппинга переведён с XLSX на CSV X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=b7310f7e31c57567f1fb9ebdf92f514811f737d6;p=erp24_rep%2Fyii-erp24%2F.git fix: экспорт маппинга переведён с XLSX на CSV PhpSpreadsheet роняет процесс по памяти на больших выборках. CSV: нет зависимостей, минимум памяти, UTF-8 BOM для Excel. Co-Authored-By: Claude Sonnet 4.6 --- 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; }