b9917a90d4b5492c8fe84fc62a72594de85a3ffe
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
86dc930915 |
refactor(backend): Sprint 3 Phase A — DealController split + N+1 fix + export streaming
Sprint 3 Phase A. Закрытие audit O-refactor-01 + O-perf-01 + O-perf-05.
O-refactor-01: DealController (802 строки) → split на 3 контроллера
по ответственности:
* DealController (466 строк) — single-resource CRUD (index/show/store/update).
* DealBulkActionController (264 строки, новый) — bulk transition/destroy/restore.
* DealExportController (120 строк, новый) — export() с streaming.
API endpoints без изменений; в routes/web.php обновлены только controller@method.
O-perf-01: N+1 в bulk-actions устранён в новом DealBulkActionController.
* transition: SELECT (id, status) → filter NO-OP → bulk-UPDATE
whereIn(changed)->update(status) + ActivityLog::insert(массив).
100 сделок: ~200 SQL → 2 SQL (после SELECT 1 UPDATE + 1 INSERT).
* destroy/restore: SELECT id'шников живых/удалённых → bulk-UPDATE
deleted_at + ActivityLog::insert. Аналогично 2 SQL вместо N.
Defense-in-depth where(tenant_id) сохранён — DealTransitionTest
«не апдейтит чужие сделки» проходит.
O-perf-05: export() переписан на OpenSpout streaming (composer require
openspout/openspout ^5.3). PhpSpreadsheet строил весь .xlsx в памяти
(10K сделок ≈ 100+ MB RAM). Теперь:
* Writer::openToFile('php://output') + Row::fromValues + chunkById(500).
* StreamedResponse → пик памяти O(1) от размера экспорта.
* CSV: OpenSpout Options(FIELD_DELIMITER=';', SHOULD_ADD_BOM=true) —
Excel-friendly RU-локаль сохранена.
* XLSX: getCurrentSheet()->setName('Сделки'), header через
Row::fromValuesWithStyle с (new Style)->withFontBold(true).
DealCreateTest.php (4 теста про export): getContent() → streamedContent()
для StreamedResponse + getCell()->getFormattedValue() для inline-string
ячеек, которые OpenSpout пишет как RichText (PhpSpreadsheet writer писал
как plain shared-string). Логика тестов и assertion'ы не меняются.
Verification:
Pest: 416/416 passed (+ 2 skipped), 1388 assertions, 47.5s.
Larastan: 0 errors above baseline.
Pint: passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
20d4682e21 |
feat(backend): Sprint 2 Phase A — Pest 4 browser scaffold + mutation + lazy-loading + Larastan cache
Sprint 2 Phase A (modernization). Закрытие audit O-stack-01/02/03 + O-perf-07:
- O-stack-01: Pest 4 browser-tests scaffold. testsuite Browser в phpunit.xml,
Pest.php extend(...)->in('Browser'), tests/Browser/SmokeTest.php
(login-flow + deal-create-flow). На Windows native PHP плагин
pest-plugin-browser требует ext-sockets (отсутствует в стандартной
сборке) и при автозагрузке вызывает socket_create_listen() ДО старта
тестов, что ломает весь Pest. Поэтому плагин НЕ в require-dev,
тесты помечены ->skip(...) с инструкцией активации на Linux CI.
Скелет тестов в комментариях — на CI достаточно
composer require pestphp/pest-plugin-browser --dev + npx playwright
install + раскомментировать тела.
- O-stack-02: infection/infection ^0.32.7 в require-dev + app/infection.json
(minMsi=50, source: Http/Models/Services). composer mutation script.
Запуск отдельной задачей в CI (медленный). allow-plugins для
infection/extension-installer.
- O-stack-03: Laravel 13 string-based lazy-loading в routes/web.php —
убран блок use App\Http\Controllers\Api\* (16 импортов), все
ссылки заменены на строки 'App\Http\...\X@method'. Контроллеры
не загружаются автозагрузчиком при boot route'ов; класс резолвится
только при матче маршрута. Использован строковый синтаксис, а не
FQN-class — Pint default preset (fully_qualified_strict_types fixer)
сворачивает FQN-class обратно в use, на строки не действует.
- O-perf-07: Larastan result cache через tmpDir: .phpstan-cache в
phpstan.neon (cache-dir не дублируется флагом — phpstan не принимает
оба источника). lefthook job 6 (larastan) использует этот же
tmpDir автоматически. Ускорение инкрементальных pre-commit прогонов:
на правке одного файла — переанализ только зависящих, не всего
графа классов. .phpstan-cache в .gitignore.
Pest: 416/416 PASS + 2 skipped (browser smoke pending Linux CI).
Larastan: 0 errors above baseline.
Infection: vendor/bin/infection --version → 0.32.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a1ea003642 |
phase2(xlsx-export): PhpSpreadsheet 5.7 + format=csv|xlsx на /api/deals/export
Закрыт TODO «реальный XLSX-export» из v1.51. Russian users prefer .xlsx
(1С/Excel) — заменяет CSV как default. CSV остаётся через format=csv.
Backend (DealController::export):
- Body теперь: {tenant_id, ids, format?: 'csv' | 'xlsx'}; default 'csv'.
- buildXlsx: Spreadsheet + setTitle 'Сделки' + setCellValue A1..G1
headers + bold(A1:G1) + setAutoSize всех колонок A..G. Writer пишет
через ob_start/php://output для возврата бинарной строки.
- Content-Type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+ Content-Disposition с .xlsx.
Quirk: PhpSpreadsheet 5.x удалил deprecated setCellValueByColumnAndRow —
мигрировал на A1-нотацию (setCellValue('A2', $val)).
Pest +4 (DealCreateTest):
- xlsx binary с Content-Type + magic bytes "PK\x03\x04" + size >2KB.
- IOFactory::createReader('Xlsx') распаковывает: sheet «Сделки» +
A1='ID' bold + A2/B2/C2 — реальные данные сделки.
- 422 на неизвестный format.
- Default (без format) — backward-compat CSV.
Frontend:
- api/deals.ts разделён: exportDeals (CSV string) + exportDealsXlsx
(Blob, responseType='blob').
- applyBulkExport(format='xlsx' | 'csv') в DealsView — default 'xlsx'.
XLSX → triggerBlobDownload (новый helper). CSV → старый CSV-helper.
На fail — fallback на local CSV.
Vitest +3 (DealsListIntegration):
- xlsx default → exportDealsXlsx + Blob download + toast «XLSX».
- 'csv' → exportDeals + toast «CSV».
- xlsx reject → fallback на local CSV + toast «Backend недоступен».
PHPStan baseline регенерирован (удалена unmatched ignore-запись для
setCellValueByColumnAndRow). cspell-glossary +дефолтит +vnd +spreadsheetml.
Регресс:
- Lint+type-check+format passed.
- Vitest 269/269 за 18.49 сек (+3 от 266).
- Vite build 982 ms.
- Pint + PHPStan passed.
- Pest 197/197 за 26.05 сек (+4 от 193, 784 assertions).
Реестр v1.61→v1.62 / CLAUDE.md v1.52→v1.53.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
374724a7a3 |
phase2(auth-2fa): TOTP-verify endpoint + TwoFactorView интеграция
- pragmarx/google2fa@^9.0 для TOTP RFC 6238.
- AuthController::login изменён: при totp_enabled=true НЕ делает Auth::login,
сохраняет auth.pending_user_id+pending_remember в session, возвращает
requires_2fa=true. /me=401 пока 2FA не пройдена.
- AuthController::verifyTwoFactor: читает pending_user_id, верифицирует TOTP
через Google2FA::verifyKey($secret, $code, window: 1) (окно ±1 = 30s).
Success → Auth::login + regenerate + clear pending + last_login_at.
- VerifyTwoFactorRequest: regex /^\d{6}$/.
- /api/auth/2fa/verify публичный (нет session-auth до verify).
Frontend:
- auth-store::login: при requires_2fa=true user остаётся null (иначе
isAuthenticated=true и guard пустит на /dashboard минуя 2FA).
- auth-store::verifyTwoFactor action.
- api/auth.ts::verifyTwoFactor(code).
- TwoFactorView: onMounted redirect на /login если нет pending state;
submit → verify → /dashboard; на error - clear code + focus first cell.
userEmail из auth.user?.email.
Pest +6 (всего 67/67 за 6.97s, 194 assertions): login для 2FA НЕ создаёт
session + verify success/неверный код/без login/валидация формата +
после verify /me=200.
Vitest +3 (всего 142/142 за 10.75s): login pending vs success state +
verifyTwoFactor success/reject. TwoFactorView spec получил setActivePinia
+ requires2fa=true для bypass onMounted-redirect.
PHPStan baseline +26 Pest TestCall warnings (накопительно).
Регресс: pint+stan passed; vitest 142/142; vite build 908ms;
story:build 21/28 за 31.28s; Pest 67/67 за 6.97s.
CLAUDE.md v1.33->v1.34, реестр Открытых_вопросов v1.42->v1.43.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
04b90afda4 |
phase2(auth-backend): Sanctum SPA mode + AuthController + 13 Pest tests
- laravel/sanctum@^4.3 install. SPA mode (cookie-based session, не tokens). personal_access_tokens migration удалена (для SPA не нужна). - AuthController (Api/): login + register + me + logout с детальной валидацией + кастомные русские error-messages. - LoginRequest + RegisterRequest Form Requests. Register требует accept_offer:accepted + accept_pdn:accepted (по ТЗ §1.5/§4.1, БЕЗ маркетингового click-wrap'а - расхождение #2 handoff vs ТЗ). - User::fillable += last_login_at, last_active_at. - Auth-routes в web.php (НЕ api.php): Sanctum SPA нуждается в session-cookie middleware из web-группы (laravel.com/docs/sanctum#spa-authentication). - cspell-words.txt: pdn, залогинен. Pest +13 (всего 61/61 за 6.22s): - login success + 2FA-flag + invalid pass + missing email + blocked + format validation + last_login_at update + register success/duplicate/без accept + me 401/200 + logout 200. - Logout-test упрощён до 200+message - Pest cookie-jar держит session между запросами теста, full flow через browser-mode (отдельный коммит). - phpstan-baseline: +25 ignored Pest TestCall warnings (Larastan+Pest quirk). Регресс: pint+stan passed; vitest 129/129 за 9.59s; vite build 802ms; story:build 21/28 за 30.39s; Pest 61/61 за 6.22s. CLAUDE.md v1.31->v1.32, реестр Открытых_вопросов v1.40->v1.41. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0eb2f72da2 |
phase1(security): roave/security-advisories — CVE-блокировка на install (Прил. Н #13)
Метапакет roave/security-advisories:dev-latest добавлен в require-dev. Это conflict-only пакет — он не содержит кода, а просто помечает известные CVE-версии других пакетов как несовместимые. При попытке установки/обновления таких версий composer падает с conflict-resolution до того, как уязвимый код попадёт в vendor/. Текущий снапшот зависимостей чист: «No security vulnerability advisories found» при установке (95 пакетов). Закрывает Прил. Н #13. Не требует конфигурации, бинарников, env. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e04f53b103 |
phase1(boost): laravel/boost v2.4.6 + Vuetify guideline (Прил. Н #10)
Установка Laravel Boost вручную (без `php artisan boost:install`):
wizard падает в обоих режимах из-за двух багов окружения:
- интерактив: PHP fatal на кириллице-пути при рендере laravel/prompts
multiselect (нет UTF-8 codepage в Windows PowerShell)
- --no-interaction: ConfiguresPrompts::multiselectFallback возвращает
null → array_map() crash в L13 framework
Manual setup вместо wizard'а:
- app/composer.json: laravel/boost ^2.4 + транзитивы (laravel/mcp v0.7,
laravel/roster v0.5.1, symfony/yaml v7.4)
- app/boost.json: agents=[claude_code], guidelines=true, mcp=true
- .mcp.json (корень): добавлен блок laravel-boost — command=php,
args=[app/artisan, boost:mcp]. Заменяет PostgreSQL MCP из фазы 0
(был убран в _comment_postgres ещё ранее).
- app/.ai/guidelines/vuetify.md: кастомный Vuetify 3 guideline по
Tooling §10.2 + Forest-палитра + 14 slug'ов воронки берутся из
schema.sql:2076 (НЕ из BRANDBOOK_v2 §3.6).
Smoke-test (boost:mcp via JSON-RPC):
- protocolVersion 2024-11-05, serverInfo "Laravel Boost"
- 9 tools: application-info, browser-logs, database-(connections|query|
schema), get-absolute-url, last-error, read-log-entries, search-docs
Уточнение к CLAUDE.md §7: «отключать guidelines Inertia/Livewire/Tailwind/
Filament/Sail/PHPUnit» — избыточно. Boost через laravel/roster детектит
установленные пакеты из composer.lock; то, что не установлено, не серверит.
Pest 4 будет detected, PHPUnit guideline не активируется (PHPUnit убран
из direct deps в коммите
|
||
|
|
03456833eb |
phase1(tooling): Pint + Larastan + IDE Helper (Прил. Н #11/#12/#14)
Установлены 3 dev-инструмента из Прил. Н фазы 1: - laravel/pint v1.29 (форматтер PHP, Прил. Н #11) — Laravel preset по умолчанию. Команды: composer pint, composer pint:test - larastan/larastan v3.9.6 + phpstan/phpstan v2.1.54 (статанализ PHP, Прил. Н #12) — level 5, paths app/bootstrap/config/database/ routes/tests. Команда: composer stan - barryvdh/laravel-ide-helper v3.7.0 (IDE-stubs, Прил. Н #14) — команда: composer ide-helper. Артефакт _ide_helper.php в gitignore Конфиги: - app/phpstan.neon — base config с includes на larastan extension и baseline. Включены checkOctaneCompatibility и checkModelProperties - app/phpstan-baseline.neon — зафиксированы 3 ошибки в default Laravel scaffold (UserFactory return type covariance + 2× ExampleTest always-true). Двигаемся вверх с этого baseline'а. Composer scripts (composer.json): - pint, pint:test (форматирование / dry-run проверка) - stan (PHPStan analyse с memory-limit 512M) - ide-helper (generate + meta) Smoke-test'ы все 3 прошли: Pint passed, PHPStan passed (с baseline), ide-helper:generate написал _ide_helper.php. Фикс окружения: на этой машине composer require падал с "Permission denied на vendor/composer/tmp-XXX.zip" из-за race condition параллельных curl'ов. Решение: COMPOSER_MAX_PARALLEL_HTTP=1 (сохранено в memory feedback_environment). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
30f0335f5f |
phase1(test): Pest 4 swap + reopen(CTO-12) — Pest 3 → Pest 4
Произошло так: при `composer require pestphp/pest pestphp/pest-plugin-laravel --dev --with-all-dependencies` я не зафиксировал `^3` — composer подтянул свежайшую Pest v4.7.0 + pest-plugin-laravel v4.1.0. Smoke-test (`./vendor/bin/pest`) на default-тестах Laravel 11 прошёл 2/2 за 281 ms — backward-compat с Pest 3 syntax подтверждён. Заказчик 08.05 (поздний вечер) принял Pest 4 после live-проверки на стеке. Бонус Pest 4: browser testing (без Dusk), stress testing, mutation testing v2. Откат дёшев — `composer require pestphp/pest:^3`. Что сделано: - composer remove phpunit/phpunit (был direct dev-dep) — phpunit остался как транзитивная зависимость Pest - composer require pestphp/pest:^4.7 pestphp/pest-plugin-laravel:^4.1 --dev - Pest.php создан через `vendor/bin/pest --init` (`tests/Pest.php`) - `vendor/bin/pest` smoke-test: 2 passed (Unit/Feature ExampleTest), 281 ms - `vendor/bin/pest --init` упал на интерактивном промпте «Wanna show Pest some love?» в non-interactive PowerShell — Pest.php к этому моменту уже создан, нефатально Reopen(CTO-12): по правилу «явная фиксация переоткрытий» обновлены 3 источника: - docs/Открытые_вопросы_v8_3.md v1.15→v1.16: блок «Что изменилось в v1.16», таблица §0 (CTO-12 переоткрыт+закрыт), запись §3 (Pest 4 + обоснование), финальный список §X (Pest 4) - docs/Tooling_v8_3.md v1.2→v1.3: блок «Что нового в v1.3», §3.1 п.4 (Pest 4 в boost:install), §3.4 строка 18 (Pest 4 + бонусы), §6 п.2 (конфликт Pest↔PHPUnit), §10.1 п.9 (процедура установки + warning про --init на Windows non-interactive) - CLAUDE.md v1.4→v1.5: §0 источники (Tooling v1.3, Реестр v1.16), §3.2 строка 18 (Pest 4), §7 п.5 (Pest 4 в boost:install), футер с историей версий Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4d38f75826 |
phase1(scaffold): Laravel 11 + predis + .env под PG 16 + Memurai
Триггер фазы 1 запущен 08.05.2026 (вечер): composer create-project laravel/laravel app Стек подтверждён native (без Docker/WSL2/Sail): - PostgreSQL 16.13 (Chocolatey, Windows-сервис, port 5432) - Memurai Developer 4.1.8 (Redis 7-совм., port 6379) — TCP +PONG OK - PHP 8.3.31 + 11/11 Laravel-required ext (pdo_pgsql, mbstring, openssl, tokenizer, xml, ctype, json, bcmath, fileinfo, curl, pgsql) - Composer 2.9.7 Что в коммите (59 файлов, 11059 строк скаффолда Laravel 11 + правки): - composer require predis/predis (v3.4.2) — PHP-only Redis-клиент, т.к. php_redis ext не установлен (см. project_phase1_strategy.md) - app/.env (gitignored) — APP_NAME=Liderra, APP_LOCALE=ru, APP_TIMEZONE=Europe/Moscow, DB_CONNECTION=pgsql → liderra@localhost, REDIS_CLIENT=predis - app/.env.example — те же правки без секретов (для команды) Smoke-test PG ↔ Laravel ↔ pdo_pgsql прошёл: 3/3 default-миграций → 9 таблиц в liderra (cache, cache_locks, failed_jobs, job_batches, jobs, migrations, password_reset_tokens, sessions, users). Артефакт стартера app/database/database.sqlite (0 B) удалён — sqlite не используется. Что НЕ в этом коммите (следующие шаги фазы 1): - Pest 3 swap (CTO-12) — composer remove phpunit + require pest - Laravel Boost MCP + 9 guidelines disable по CLAUDE.md §5/§7 - Pint, Larastan, Roave/SecurityAdvisories, IDE Helper, squawk, pgFormatter (Прил. Н #11–18) - resources/boost/guidelines/vuetify.blade.php Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |