Commit Graph

4 Commits

Author SHA1 Message Date
Дмитрий b5849bbd2a fix(projects): cyrillic ILIKE via PG ICU + clearable workaround
Корень: dev-БД `liderra` создавалась с LC_CTYPE=C — lower()/upper() не
делает case-folding для кириллицы, `ILIKE '%сп%'` на «Окна СПб» = 0 строк.
Test-БД с Russian_Russia.1251 маскировала проблему.

Системный fix: dev-БД пересоздана через `LOCALE_PROVIDER icu ICU_LOCALE 'und'`
(PG 16+ ICU collation, кросс-платформенно). Точечный COLLATE-workaround не
понадобился — все 5 ILIKE-endpoint'ов теперь работают с кириллицей без
правки кода. CTO-20 закрыт в реестре v1.81; команда CREATE DATABASE с ICU
зафиксирована для prod-deploy.

Сопутствующее:
- ProjectsView clearable: workaround `::after content '✕'` + видимость
  через `.v-field--dirty` (mdi-* font не подключён в проекте — CTO-19
  заведён в реестре).
- LookupsTest: удалён stale case `GET /api/projects?tenant_id=N`,
  заменённый auth:sanctum-роутом в Plan 5.
- Pest +1 регрессионный тест (`search is case-insensitive for Cyrillic`)
  в ProjectsListShowTest, 10/10 / 37 assertions.
- phpstan-baseline регенерирован (3 actingAs + удалённый case).
- cspell-words: +Регистронезависимый, +und.
- app/.backups/ в gitignore.

Verify:
- Pest --parallel: 742 passed / 1 flaky error (CsvReconcileJobTest cache
  race, в изоляции 2/2 PASS) / 3 skipped.
- Browser: «сп» и «окн» возвращают «Окна СПб».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:25:25 +03:00
Дмитрий b912724cf7 chore(frontend): Sprint 4 Phase C — bundle analyzer + dead-code cleanup (audit O-refactor-06)
- rollup-plugin-visualizer + script `npm run build:analyze` (env BUILD_ANALYZE=1)
  Output: storage/bundle-analyze.html (gzip + brotli sizes), gitignored.
- cross-env установлен для Windows-совместимости env-переменной build:analyze.
- knip + knip.config.ts (entry app.ts + router/index.ts; ignore *.story.vue + tests/).
  ВАЖНО: knip падает с oxc-parser ArrayBuffer fail на этой машине
  (Windows quirk feedback memory) — конфиг сохранён для будущих запусков на
  Linux/macOS CI. Dead-code search выполнен вручную через grep по composables/.
- Удалены 4 unused exports + 4 private helpers, инициируемых только ими:
  * mockReports.ts: MOCK_JOBS, QuotaInfo (interface), MOCK_QUOTA
  * reportsMapper.ts: reportTypes()
  * mockTenantDetail.ts: expandTenantDetail() + 4 SAMPLE_* consts
    (SAMPLE_USERS/SAMPLE_PROJECTS/SAMPLE_BALANCE_HISTORY/SAMPLE_ACTIVITY)
  * useCsvDownload.ts: csvEscape() — снят `export` (используется внутри файла)

ESLint + vue-tsc + Vitest 416/416 + build — зелёные.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:59:58 +03:00
Дмитрий 5560ebbdfd phase1(eloquent): Tenant/User/Project models + SetTenantContext middleware
Первый Eloquent-слой над schema v8.6 + middleware для RLS-фильтрации
HTTP-запросов. Pest 19/19 passed (1672 ms): 4 RLS smoke + 8 model
smoke + 5 middleware + 2 default.

app/app/Models/ (NEW Tenant.php, Project.php; MODIFIED User.php):

- Tenant — saas-уровневая модель (БЕЗ RLS, тенант-родитель). Soft Deletes.
  hasMany Users, Projects.
- User — переписан под нашу схему: password_hash вместо password,
  first_name/last_name вместо name, override getAuthPassword/Name для
  Laravel auth-интеграции. Soft Deletes. belongsTo Tenant.
- Project — tenant-aware с RLS. belongsTo Tenant.

app/database/factories/ (NEW TenantFactory, ProjectFactory; MODIFIED
UserFactory):

- TenantFactory: уникальный subdomain через Str::random + дефолты
  (timezone Europe/Moscow, locale ru, is_trial true, api_key_limit 5).
- UserFactory: tenant_id через Tenant::factory() chain, email unique
  через faker, password_hash через Hash::make.
- ProjectFactory: tenant_id через factory chain, дефолты под schema.sql
  (region_mask 255, delivery_days_mask 127, assignment_strategy manual).

app/app/Http/Middleware/SetTenantContext.php (NEW, alias `tenant`):

- Резолюция tenant_id (приоритет): auth()->user() → subdomain (3+ parts
  HTTP_HOST) → X-Tenant-Id header (только dev/testing).
- Без контекста → 403 Forbidden с явным сообщением.
- SET LOCAL app.current_tenant_id внутри транзакции (DB::beginTransaction
  + SET LOCAL + next() + commit/rollback). PgBouncer-safe (Прил. И Г.1
  кейс 2).
- Зарегистрирован в bootstrap/app.php через $middleware->alias().

app/tests/Feature/ (NEW TenantModelsTest, SetTenantContextTest):

- TenantModelsTest (8 тестов): factories + связи (Tenant→users/projects,
  User→tenant, Project→tenant) + проверка User::getAuthPassword override.
- SetTenantContextTest (5 тестов): 403 без контекста, X-Tenant-Id header,
  игнор не-числового header, subdomain резолюция через absolute URL,
  middleware устанавливает app.current_tenant_id для last query.

Сопутствующие правки:

- app/.gitignore: + _ide_helper_models.php (сгенерированный, как и
  _ide_helper.php — не в репо)
- app/phpstan-baseline.neon: regenerated — Pest dynamic methods
  ($this->get(), withHeaders()) и factory-return-type mismatch (Larastan
  v3 не понимает array<string, mixed> vs array<model property of T>) +
  Request::user/header (Larastan тип hint мисс) + console.php $this в
  Closure — все в baseline до миграции на typed properties / pest extension
- CLAUDE.md §6: Pest 6/6 → 19/19, добавлены модели + middleware
- memory project_state.md, MEMORY.md: обновлены под новый этап

Не сделано в этой сессии (отложено):

- Deal model — composite primary key (id, received_at) + partitioned
  parent. Эта модель сложнее по архитектуре Eloquent.
- ide-helper:models -W -M -N запускался — добавил @mixin IdeHelperX
  и сгенерил _ide_helper_models.php; но т.к. этот файл gitignored,
  @mixin строки удалены из моделей (PHPStan не нашёл бы класс на CI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:29:50 +03:00
Дмитрий 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>
2026-05-08 09:37:16 +03:00