v2.1 — Multi-tenant cleanup complete
Завершение архитектурного сдвига v2.0. Все legacy callsites мигрированы, e2e-тесты подтверждают изоляцию tenants.
Что приехало
Migrated 45 of 68 callsites (через скрипт + ручная проверка). Bulk-pattern:
- Controllers (inheriting `ApplicationController`) → `current_company` helper
- API / services / jobs / models / mailers → `Current.company || Company.kept.first` (explicit fallback chain)
Files touched (40+ контроллеров и сервисов):
- 7 AI controllers (`ai/applicants`, `ai/kpi`, `ai/leaves`, `ai/offboarding`, `ai/onboarding`, `ai/openings`, `ai/rounds`)
- 14 главных контроллеров (`dashboard`, `employees`, `job_openings`, `leave_requests`, etc)
- 12 settings контроллеров
- Mailers, helpers, jobs, models, notifiers, services
Remaining 23 callsites — все explicit `Current.company || Company.kept.first` fallback chains, не legacy. Это и есть целевой v2.0 pattern.
E2E multi-tenant spec (10 tests)
`spec/requests/multi_tenant_spec.rb`:
- ✅ Subdomain resolution: `acme.example.com` → `Company.find_by(subdomain: "acme")`
- ✅ Apex fallback: `example.com` → first company (single-tenant compat)
- ✅ Unknown subdomain → 404 (HTML for browser, JSON for /api/ paths* with `{error: "tenant_not_found"}`)
- ✅ Ignored subdomains (`www` / `api` / `app`) → fallback OK
- ✅ AppSetting isolation: per-tenant settings не пересекаются (verified with 2 real companies)
- ✅ Employee scope: company-scoped queries возвращают только свои
- ✅ `current_company` helper: delegates to `Current.company`, falls back to first
Quality
- 142 examples (+10 multi-tenant), 0 failures
- Rubocop: 0 offenses on 259 files
- Zeitwerk: clean
Что это даёт
Теперь HRMS реально готов крутиться в SaaS-режиме:
- `acme.hrms.example.com` → видит только данные Acme
- `zenith.hrms.example.com` → видит только данные Zenith
- `hrms.example.com` (без поддомена) → fallback на первую компанию (single-tenant установка работает как и раньше)
Hosted demo на одной инстансе для нескольких клиентов теперь технически возможен.