Commit Graph

441 Commits

Author SHA1 Message Date
Дмитрий 80de6ecbbd fix/auth: косяк 05 — неподтверждённая почта зовёт подтвердить, а не «заблокирован»
При входе с email_verified_at=null (новый клиент не ввёл код из письма)
AuthController возвращает дружелюбное «Подтвердите почту — мы отправили код
на ...» с флагом email_not_confirmed, а не пугающее «Аккаунт заблокирован».
Реальная блокировка админом (verified + is_active=false) по-прежнему даёт
«Аккаунт заблокирован». Фронт по флагу уводит на /confirm-email с переносом
email в store (там кнопка «Отправить код повторно»).
TDD: Pest 2 кейса (неподтверждённый vs заблокирован), vitest redirect-кейс,
все GREEN. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:15:06 +03:00
Дмитрий f7963bcfb3 fix/projects: нормализация телефона-источника + понятная ошибка и подсказка
Косяк 02: поле телефона проекта типа call отвергало +7.., 8.., пробелы и скобки.
prepareForValidation в StoreProjectRequest и UpdateProjectRequest приводит номер
через PhoneNormalizer к канону 7XXXXXXXXXX без ведущего плюса, чтобы раздача
LeadRouter матчила без плюса. Финальная regex оставлена страховкой.
Кастомные messages по signal_type: ошибка с примером формата, без имени Источник.
Фронт: постоянная подсказка под полем в NewProjectDialog и ProjectDetailsDrawer.
TDD: ProjectPhoneNormalizationTest 8 кейсов, GREEN. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:04:58 +03:00
Дмитрий 116b0aaa42 fix/billing: префлайт и блокировка баланса берут действующую версию тарифа по дате
Косяк 01: расчёт capacity садился на устаревшую цену через
PricingTier::where is_active true при двух активных версиях тарифа.
Переведено на PricingTierRepository activeAt now во всех путях расчёта:
release-сервис, контроллер runPreflight, bulk-лимит, sweep-джоб, reminder-джоб.
Реальное списание было корректным — деньги клиентов не затронуты.
TDD: PreflightUsesCurrentTariffVersionTest 5 кейсов, GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:03:55 +03:00
Дмитрий 146174a2e2 feat: публичные юр-страницы оферта/политика/возврат + реквизиты + страница цен под модерацию ЮKassa
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-24 09:31:52 +03:00
Дмитрий 8696b5e27f feat/billing: F/J — единый расчёт замков + пополнение/пересчёт снимают оба замка
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
- D2: requiredLeadsForTomorrow переведён на полный лимит, откат share-aware R-19 [решение владельца]
- B/D3: пополнение снимает клиентскую заморозку И блоки проектов вместе, политика всё-или-ничего
- F/J/D6: вечерний пересчёт 18:00 снимает блоки проектов у незаморожённых; общий ProjectBlockReleaseService; иерархия заморозка > блок
- fix: balance_freeze_log INSERT переведён на главное соединение — межсессионный self-lock с FOR UPDATE топапа [найден живым прогоном, pg_blocking подтвердил; в тестах маскировался SharesSupplierPdo]
- spec + plan в docs/superpowers

138/138 биллинг-тестов GREEN. Pint чисто. Живьём B+F подтверждены на докалке. На прод НЕ катилось.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:58:44 +03:00
Дмитрий 1827a38fe5 feat/billing: H — метка «Приостановлен — не хватает баланса» на карточке проекта
Заблокированный за нехваткой баланса проект выглядел как обычный «Ожидает
синхр.» — клиент не понимал, почему лиды не идут. Теперь карточка показывает
приоритетный красный статус «Приостановлен — не хватает баланса».

- ProjectResource: новое read-only поле balance_blocked (preflight_blocked_at !== null)
- ProjectCard: статус-бейдж приоритетно показывает блок над sync_status
- Project type: balance_blocked?: boolean

TDD: backend 2/2 (ProjectResource), frontend ProjectCard 6/6. ProjectResource
регрессия (applies_from/source_lock) 6/6 GREEN.

larastan/deptrac исключены точечно — пред-существующая краснота.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:32:42 +03:00
Дмитрий a711d474d8 feat/billing: E — авто-снятие preflight_blocked_at при пополнении, всё-или-ничего
Пополнение баланса больше не оставляет проекты заблокированными навечно.
Новый ProjectBlockReleaseService: после зачисления (единая точка
BillingTopupService::topup — и ручное пополнение, и онлайн через
PaymentWebhookController) проверяет, хватает ли баланса на суммарный дневной
лимит ВСЕХ активных проектов тенанта, включая заблокированные. Хватает →
снимает preflight_blocked_at со всех + диспатчит SyncSupplierProjectJob;
не хватает → не трогает никого и возвращает дефицит (политика всё-или-ничего,
решение владельца). Зеркалит BalancePreflightService и фильтр sweep.

TDD: 4 теста (release при покрытии, удержание при нехватке, всё-или-ничего на
двух проектах, no-op без заблокированных). Регрессия billing 114/114.

larastan/deptrac исключены точечно — пред-существующая краснота.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:33:14 +03:00
Дмитрий 4e1cc951b8 feat/billing: G — все пути синхронизации уважают preflight_blocked_at
Заблокированный за нехваткой баланса проект не должен уезжать заказом к
поставщику ни через одиночную правку, ни через ручную «Синхронизировать»,
ни через возобновление — раньше эти три пути диспатчили SyncSupplierProjectJob
безусловно. Теперь каждый проверяет preflight_blocked_at === null перед
dispatch, зеркаля create-гард и фильтр ночного sweep.

- ProjectService::update — needsResync && preflight_blocked_at === null
- ProjectService::triggerSync — early return для заблокированного
- ProjectController::toggleActive — гард перед dispatch

TDD: 6 тестов (3 пути × blocked/unblocked) — assertNotPushed для заблок.,
assertPushed для обычного. Регрессия preflight/project actions 26/26.
Живой контраст на докалке: blocked → очередь 0, unblocked → очередь 1.

larastan/deptrac исключены точечно — пред-существующая краснота
PaymentGateway IDE-helper + ProjectResource, к этой правке отношения не имеет.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:17:21 +03:00
Дмитрий 2deaf2075a feat/billing: N — массовое изменить-лимит уважает баланс + видимый тост причин пропуска
bulkUpdateLimit обходил преfflight баланса: клиент мог выставить дневной
лимит выше ёмкости и заказать у поставщика больше оплаченного. Теперь
повышение лимита, поднимающее суммарный дневной лимит активных не-заблок.
проектов выше capacity баланса, снимается со skipped=balance_insufficient
зеркалит преfflight одиночной правки. Понижения и правки paused/blocked —
всегда проходят. Без активных pricing_tiers проверка пропускается.

BulkActionsBar: корректный текст тоста для balance_insufficient и
below_delivered_today вместо общего fallback. ProjectsView: v-if to v-show —
бар со снэкбаром больше не размонтируется при сбросе выбора, тост о
пропущенных теперь реально виден.

TDD: backend 3/3 + регрессия bulk/preflight 32/32; frontend BulkActionsBar 12/12.
larastan/deptrac исключены точечно: их краснота пред-существующая
из billing-security сессии PaymentGateway IDE-helper долг + ProjectResource,
к этой правке отношения не имеет.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:06:46 +03:00
Дмитрий 3391cc7a49 feat/billing: blocked project не уходит к поставщику при создании + разбор и план
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:30:27 +03:00
Дмитрий 3b142f9375 fix(billing-security): хардненинг webhook ЮKassa + чистка admin-auth комментариев
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Webhook (PaymentWebhookController): строгий матч gatewayPaymentId===paymentId
(confused-deputy), проверка валюты RUB (WebhookVerifyResult.currency), IP-allowlist
services.yookassa.webhook_ip_allowlist (fail-open при пустом). web.php: убраны
устаревшие «MVP без auth» комментарии — saas-admin зона fail-closed (nginx-basic
+ M-1 REMOTE_USER allowlist, проверено на проде). +3 теста, 11/11 зелёные.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 04:15:48 +03:00
Дмитрий 2abc9a3a09 Merge branch 'feat/source-edit-lock-ux' 2026-06-23 03:48:18 +03:00
Дмитрий 415536c434 feat(billing): provenance — saas_transactions.balance_transaction_id, связка оплата->журнал
Закрыт хвост из billing-audit: webhook при зачислении пишет id строки ledger
в saas_transactions.balance_transaction_id (жёсткая прослеживаемость). Колонка
BIGINT nullable без FK (balance_transactions партиционирована). schema.sql v8.52
+ миграция 2026_06_22_170000 (guarded) + CHANGELOG. Тест проверяет связку. 115/115.
2026-06-22 22:30:36 +03:00
Дмитрий 6ad618c715 feat(billing): админ-эндпоинт ввода ключей ЮKassa с шифрованием config 2026-06-22 21:45:26 +03:00
Дмитрий f0fe6a1cde feat(billing): PaymentWebhookController — идемпотентное зачисление по server-to-server сверке
Публичный роут /api/webhook/payment (CSRF-exempt). Cross-tenant поиск через
pgsql_supplier (BYPASSRLS), зачисление под SET LOCAL app.current_tenant_id,
атомарный claim pending->success (идемпотентность), защита от несовпадения
суммы, делегирование зачисления BillingTopupService.
2026-06-22 21:43:01 +03:00
Дмитрий a9714c8c5d feat(billing): развилка topup по флагу billing_yookassa_enabled — шлюз vs заглушка
Флаг ВКЛ → создание платежа через OnlineTopupService + confirmation_url;
ВЫКЛ → прежнее мгновенное зачисление. Биндинг PaymentGatewayDriver в
AppServiceProvider. Также мелкая гигиена SystemSettingsHelperTest
(DatabaseTransactions для отката).
2026-06-22 21:36:30 +03:00
Дмитрий 789c2622b3 feat(billing): PaymentGatewayManager + OnlineTopupService — создание онлайн-платежа 2026-06-22 21:08:46 +03:00
Дмитрий 08cf23893a feat(projects): source-lock state для UI (guard.lockState + ProjectResource + анти-N+1)
SupplierSnapshotGuard::lockState (pure, без DB) + ProjectResource отдаёт source_locked/source_unlock_at/source_unlock_projected; ProjectController withCount(supplierProjects). Логика гарда не изменена.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:52:18 +03:00
Дмитрий 60c640e88a feat(db): RLS на tenants + created_at TZ (schema v8.51)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-22 18:03:42 +03:00
Дмитрий cab741f8d8 fix(приёмка): FN-AUDIT — verify-chains авто-закрывает устаревшие инциденты целой цепочки
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
При зелёной проверке всех партиций таблицы audit:verify-chains теперь закрывает
оставшиеся открытые инциденты разрыва hash-chain по этой таблице. Убирает класс
вечно-открытых ложных инцидентов после транзиентного разрыва — например строк
тест-тенантов приёмки, удалённых teardown.

Диагностика прогона 22.06: 4 m06-инцидента 576-579 были только по строкам
тест-тенантов; teardown их удалил, боевые цепочки tenant 2 целы.

TDD: 2 теста (целая таблица закрывает инцидент; сломанная — не трогает).
Pint и Larastan чисто, регрессий нет.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:57:10 +03:00
Дмитрий 37a8a5c17f fix(приёмка): FN-2 + FN-3 — мёртвый reminder в DEFAULT и email_verified_at при confirm
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
FN-3: онбординг-confirm не ставил users.email_verified_at — поле вне fillable,
mass-assign его гасил. RegistrationService::confirm теперь forceFill is_active +
email_verified_at. TDD ConfirmSetsEmailVerifiedAtTest.

FN-2: миграция 2026_06_19_130000 дропнула таблицу reminders, но забыла
ALTER COLUMN notification_preferences SET DEFAULT — реальный DB-дефолт оставался
с мёртвым ключом reminder, расходясь с каноном schema.sql v8.45. Новая миграция
2026_06_22_120000 метаданные-only выравнивает дефолт под канон. squawk 0 issues,
применено dev+testing, проверено psql. CHANGELOG v8.50 + шапка schema.sql.
TDD NotifPrefsDefaultNoReminderTest.

FN-1 переоценён как не-баг кода: resolvePlatforms даёт B2+B3 корректно, B3-miss —
supplier-side Doubles, джоба сама ретраит. Зафиксировано в отчёте приёмки.

Прод не трогался. Накат — позже вместе с остальным.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:20:42 +03:00
Дмитрий e8e5c82b86 fix(приёмка): FN-RESET + FN-LOGIN-ROUTE + диагностируемость FN-SESSION
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
FN-RESET: письмо сброса строило именованный роут password.reset которого нет в SPA.
ResetPassword::createUrlUsing → /reset/{token}?email= в AppServiceProvider boot.

FN-LOGIN-ROUTE: гость без Accept json на auth:sanctum уводил в именованный роут
login которого нет → 500. redirectGuestsTo /login + render AuthenticationException
→ 401 JSON для api/*.

FN-SESSION: chromium.launch стоял вне try/catch — отказ запуска браузера маскировался
unhandled-rejection в opaque exit 1 двойник login-rejected. launch в try + top-level
catch → чистый exit 4 + JSON stderr в refresh-session.js и manage-project.js.

Тесты: PasswordResetUrlTest, UnauthenticatedApiResponseTest, node:test launch-failure
в обоих playwright-скриптах. Разбор FN-SESSION + ops-долг playwright install под
www-data + поправки отчёта приёмки + новая находка FN-INN-LOOKUP.

Прод не трогался. Накат — позже вместе с остальным.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:49:19 +03:00
Дмитрий 89ed1714e8 style(test): импорт Collection в digest-тесте (lint-фиксап, функц. идентично)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:45:21 +03:00
Дмитрий 2225a8487e feat(security): рабочая «Завершить сессию» — реальный отзыв активных сессий
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
UI-аудит: вкладка Безопасность показывала фейк-сессии с мёртвой кнопкой.
Теперь — реальные активные сессии + рабочий отзыв.

- UserSessionTracker (новый): запись сессии при входе (login + 2FA verify +
  recovery-use) в существующую таблицу user_sessions; отзыв = удаление строки
  + удаление сессии из Redis по session_id (реальный выход с устройства);
  logout снимает текущую сессию из списка. Best-effort (не ломает вход/выход).
- AccountController: GET /api/account/security отдаёт реальные сессии;
  DELETE /api/account/sessions/{id} — отзыв (только свои; чужая → 404).
- Фронт SessionsTable: список + кнопка «Завершить» (кроме текущей).
- phpstan-baseline обновлён (Pest-$this нового теста).

Pest: 10/10. Верификация: Playwright (2 сессии → «Завершить» → исчезла).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:15:26 +03:00
Дмитрий e15494c8ad fix(приёмка): N-2 детерминированная уборка orphan-таблиц + N-4 надёжный дедуп дайджеста
N-4: пометка digest_sent ПОСЛЕ отправки (mark-after-send) — повтор не
дублирует И сбой джоба до отправки не теряет дайджест. TDD-тест GREEN 6/6.
N-2: инъекции R2 с TEST-диапазоном vid 9e12..1e13 → R5 чистит 4 orphan-таблицы
детерминированно по vid/lead_id, без ручного манифеста.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 16:10:57 +03:00
Дмитрий ec6434ee9a feat(security): реальная смена пароля + недавние входы вместо mock-заглушек
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
UI-аудит раунд 2, вкладка «Безопасность» — убраны фейк-данные и мёртвые кнопки.

Backend (schema-free, без смены SESSION_DRIVER):
- AccountController + ChangePasswordRequest: POST /api/account/change-password —
  проверка текущего пароля (Hash::check против password_hash), новый >=10 симв +
  confirmed, лог password_changed/password_change_failed в auth_log (hash-chain).
- GET /api/account/security: last_password_change_at (max по password-событиям
  auth_log) + recent_logins (реальные login_success: устройство/IP/время).
- Роуты под auth:sanctum + throttle:auth-password.
- Pest: 6 тестов. Регрессия Account+Auth — 23/23 GREEN. phpstan-baseline обновлён
  (Pest-$this false-positives нового теста, как у прочих тестов).

Frontend:
- api/account.ts.
- ChangePasswordCard: реальная дата + диалог (текущий/новый/подтверждение,
  show/hide, обработка 422 неверного текущего пароля).
- SessionsTable -> «Недавние входы»: реальный список из API, убраны 3
  захардкоженных фейк-сессии + мёртвая кнопка «Завершить».

NB: индивидуальный отзыв cookie-сессий требует database-драйвера сессий
(инфра-решение владельца) — отдельный follow-up. Сейчас входы — честный read-only.

Верификация: type-check, build, Playwright (диалог: неверный->ошибка,
смена->дата 21.06, восстановление password123; недавние входы — реальные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 15:03:43 +03:00
Дмитрий 0c26afd03e fix(admin): починка 500 на admin-сохранениях (audit-id из config, без saas_admin_users) + тариф текущей датой
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-21 11:32:53 +03:00
Дмитрий 7606e69dbc fix(captcha): актуальный validate-URL Yandex SmartCaptcha smartcaptcha.cloud.yandex.ru (сверено по докам)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
2026-06-21 09:15:20 +03:00
Дмитрий 12eace3699 feat(security): реальный Yandex SmartCaptcha драйвер самозаписи по CAPTCHA_DRIVER (M-2)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-21 09:02:31 +03:00
Дмитрий d7b5f2c103 test(coverage): расширение охвата Раздел B №2-5 — границы reminder/final, терминальные пути оркестратора, G6 API edges 2026-06-21 08:41:03 +03:00
Дмитрий b9184a6aea test(supplier): regression-guard неизменности received_at при merge CSV-recovered сделки 2026-06-21 08:23:13 +03:00
Дмитрий d784df50a8 fix(security): fail-closed app-гейт админ-зоны по REMOTE_USER и allowlist — M-1 2026-06-21 08:11:02 +03:00
Дмитрий 255680cf0f feat(api): rate-limit публичного /api/v1/deals 120 в минуту на источник перед apikey 2026-06-21 08:10:22 +03:00
Дмитрий 825b02cf72 fix(digest): идемпотентность дайджеста новых сделок по сделке (N-4)
N-4: SendNewLeadsDigestJob выбирал сделки received_at>now()-30min без маркера
отправки → ручной/повторный прогон (R3b велит дёргать вручную) дублировал
письмо-дайджест. Окно «непересекается» только при ровно-30-мин прогонах.

Фикс без схемы: идемпотентность по сделке через Redis SETNX
(Cache::add 'digest_sent:<id>', TTL 1 сутки) — паттерн как rate-limit
ZeroBalancePausedMail. Уже отправленная сделка в дайджест повторно не входит.

TDD: тест «повторный прогон НЕ дублирует» (RED 2 письма → GREEN 1), сюит 5/5.
R3b DIGEST-ON + owner-decisions + NEW-статус обновлены (N-4 → закрыто).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:01:10 +03:00
Дмитрий 862243a2b8 fix(reports): защита от CSV/formula-инъекции в писателях-форматтерах (F-CSV-wide)
Ревью раунда 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>
2026-06-21 05:23:42 +03:00
Дмитрий 6b9a7636ae fix(export): защита выгрузок сделок от CSV/formula-инъекции
F-CSV: ячейки экспорта писались «как есть» — значение вроде =HYPERLINK(...)
или @SUM(...) в комментарии/контакте/городе при открытии файла в Excel/
LibreOffice исполнялось как формула (OWASP Formula Injection).

Новый App\Support\CsvFormulaGuard::neutralize() префиксует апострофом ячейку,
начинающуюся с = + - @ TAB CR. Применён к свободному тексту в:
  - DealExportController (телефон/источник/город/статус/комментарий)
  - DealsExportProvider  (телефон/контакт/статус/проект/менеджер)
Числовые колонки (id/суммы/даты) не трогаются — ведущий «-» там легитимен.
TenantChargesController НЕ уязвим: колонки enum(CHECK)/число/дата, свободного
текста нет.

TDD: 13 unit-тестов хелпера + feature-тест экспорта сделок + provider-тест.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:45:30 +03:00
Дмитрий c5840ea25d refactor(impersonation): отправку письма о завершении сессии вынести в сервис
F-DEPTRAC: middleware ImpersonationContext зависел от слоя Mail
(ImpersonationEndedMail) — нарушение deptrac ruleset, блокировало ВСЕ
php-коммиты. Отправка письма + завершение сессии вынесены в новый
ImpersonationExpiryService (Service-слой, которому Mail разрешён,
идемпотентно). Middleware теперь зависит только от Service.

deptrac: 0 нарушений. TDD: 2 теста сервиса (письмо в очереди + идемпотентность).
phpstan-baseline.neon перегенерён под Pest $this-false-positives новых тестов
этой серии правок приёмки (level 5, composer stan = 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 04:43:16 +03:00
Дмитрий a2f086cc40 feat(G7-B): leave из кабинета + impersonation-контекст в /api/auth/me + end-письмо 2026-06-19 17:04:48 +03:00
Дмитрий 56f54dfdb7 feat(G7-B): изоляция админ-зоны при impersonation + авто-истечение сессии 1 ч 2026-06-19 16:51:21 +03:00
Дмитрий 3e1eb7e835 feat(G7-B): guard impersonation + envelope машинного ключа на рабочих группах кабинета
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 16:44:42 +03:00
Дмитрий 377a16a605 feat(G7-B): verify — session-takeover целевого юзера + выдача машинного ключа 2026-06-19 16:35:03 +03:00
Дмитрий ab0787c887 feat(G7-B): письма impersonation код-согласие + завершение, wire в init 2026-06-19 16:25:49 +03:00
Дмитрий bfdc45b757 feat(G7-A): POST /api/support-requests + тест (store+mail+валидация)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:00:12 +03:00
Дмитрий 2eaa78f95b fix(stan): Larastan-долг G1/G6 = 0 ошибок (реальные баги — починены, не спрятаны)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Продуктовый код (фиксы, не baseline): TenantRequisites+SupplierLead — явные @property (ide-helper:models пропускал модели); DealsController V1 — лишний ?-> на non-null received_at; ScrubPii — guard instanceof Monolog. Тест-код: ImitationTestCase @param int; findByInn return-type. Baseline перегенерён — в нём ТОЛЬКО ложноположительные (Pest TestCall + защитный ?-> на nullable first() в debug-строках ScenarioBC), 0 продуктовых подавлений (проверено диффом). composer stan: 0.

NB: столбцы lead-region (dadata_qc/phone_operator/region_source/resolved_subject_code) есть в БД, но отсутствуют в db/schema.sql — отдельный дрейф схемы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:40:00 +03:00
Дмитрий 6bbfa1f624 fix(G3): убрать создание reminders из HistoricalImportService (шов CSV-импорта)
CSV-колонку «Напоминание» парсер по-прежнему читает (внешний формат), но строки-напоминания больше не создаются — модель Reminder удалена.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:43:00 +03:00
Дмитрий 582c02d4a7 feat(G3): убрать reminder из дефолтов notification_preferences (factory+seeder)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:38:58 +03:00
Дмитрий 34981da707 feat(G3): удалить контроллер/роуты/модель/команду/письмо напоминаний
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:37:57 +03:00
Дмитрий c85b4acbc3 feat(G3): убрать ветку reminder из NotificationService
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:36:27 +03:00
Дмитрий 591abc7d93 feat(G3): убрать next_reminder_at-подзапрос из DealController
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:34:36 +03:00
Дмитрий c61c38efd4 test(G6): приёмка публичного API сделок (RED)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:11:24 +03:00