862243a2b8
Ревью раунда 2: F-CSV шире — формула может уйти не только через DealExport, но
и через центральные писатели ОТЧЁТОВ (Managers/Sources/Billing/Deals × csv/xlsx).
Прежний фикс нейтрализовал в DealsExportProvider — неверный слой: он кормит и
JSON-формат, где апостроф портил данные (ReportJobControllerTest).
Перенос защиты в писатели:
- CsvFormatter — CsvFormulaGuard::neutralizeCell (апостроф, числа не трогаются)
- XlsxFormatter — опасные строки через setCellValueExplicit TYPE_STRING
(Excel НЕ вычисляет формулу; XLSX опаснее — считает без предупреждения)
- DealsExportProvider — откат к сырым данным (JSON больше не портится)
CsvFormulaGuard: + isDangerous()/neutralizeCell() — numeric-aware (ведущий «-»
числа не экранируется).
TDD: unit-тесты CsvFormatter + XlsxFormatter (load xlsx → datatype 's', не 'f').
DealExportController (OpenSpout, отдельный путь Сделок) — без изменений.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
30 lines
1.2 KiB
PHP
30 lines
1.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Reports\Formatters\XlsxFormatter;
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
|
|
// F-CSV (wide): XLSX опаснее CSV — Excel вычисляет формулу из ведущего «=» без
|
|
// предупреждения. Опасные строки пишем как явный текст (TYPE_STRING),
|
|
// числа остаются числами.
|
|
|
|
it('пишет formula-строку как текст, число оставляет числом', function () {
|
|
$content = (new XlsxFormatter)->format(
|
|
['Контакт', 'Сумма'],
|
|
[['=1+1', 1500]],
|
|
);
|
|
|
|
$tmp = tempnam(sys_get_temp_dir(), 'xlsxguard').'.xlsx';
|
|
file_put_contents($tmp, $content);
|
|
$sheet = IOFactory::load($tmp)->getActiveSheet();
|
|
|
|
// A2 — опасная строка: хранится как литеральный текст, НЕ как формула
|
|
expect($sheet->getCell('A2')->getValue())->toBe('=1+1');
|
|
expect($sheet->getCell('A2')->getDataType())->toBe('s');
|
|
// B2 — число остаётся числом
|
|
expect($sheet->getCell('B2')->getValue())->toBe(1500);
|
|
|
|
@unlink($tmp);
|
|
});
|