Commit Graph

2630 Commits

Author SHA1 Message Date
Дмитрий 1a6ef60d07 docs(пилот): снимок 25.06 — квирк 107 закрыт (config.php www-data, redeploy+валидатор), schema v8.53, тест-стенд 55→0, U1/B1 ждут выката
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:23:03 +03:00
Дмитрий f89df8310a fix(ops): закрыть повтор квирка 107 — optimize последним в redeploy + валидатор проверяет читаемость
Проверено на боевом 25.06: config.php = ubuntu:www-data mode 775, www-data ЧИТАЕТ его
через группу, портал HTTP 200 — реального дефекта НЕТ. Квирк «лечили» несколько раз,
гонясь за строгой проверкой «владелец == www-data», тогда как важна читаемость.

Две первопричины повтора закрыты:
1. deploy/redeploy.sh: optimize перенесён в КОНЕЦ (после chown -R ubuntu:www-data
   bootstrap/cache). Раньше optimize шёл ДО chown'а → chown переписывал владельца
   свежего config.php обратно на ubuntu. Теперь кэши пишутся после chown и остаются
   www-data:www-data.
2. .claude/agents/prod-deploy-validator.md П1: критерий сменён на ЧИТАЕМОСТЬ www-data
   (sudo -u www-data test -r config.php) вместо строгого владельца. Владелец ubuntu при
   группе www-data+775 больше НЕ даёт ложный NO-GO. Свежесть (mtime ≥ .env, квирк 104)
   сохранена. Описание квирка 107 и формат рапорта обновлены.

Прод не трогали — он здоров. После следующего redeploy config.php станет www-data-owned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:07:24 +03:00
Дмитрий 9c29834baa db: canon-sync schema.sql audit_chain_hash() с миграцией advisory-lock (v8.53)
Тело функции audit_chain_hash() в db/schema.sql приведено в соответствие с миграцией
2026_05_30_000001_add_advisory_lock_to_audit_chain_hash: добавлен per-partition
pg_advisory_xact_lock(lock_key из TG_RELID) против разветвления hash-цепочки при
конкурентных INSERT. Канон отставал — миграция уже live (migrate:fresh даёт функцию
С блокировкой), тело schema.sql её не содержало.

Структурно БД НЕ меняется: хеш-формула verbatim, прод корректен через миграцию,
счётчики таблиц/индексов/RLS/функций/триггеров без изменений. Синхронизирован
только текст канона + COMMENT. Запись в db/CHANGELOG_schema.md (§8). Проверено:
migrate:fresh грузится чисто, функция HAS_LOCK, AuditChainRaceConditionTest зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:52:00 +03:00
Дмитрий 802105089d test: дозакрытие последних 5 — advisory-lock наблюдение, cap-3, webhook фаза-3, supplier-client URL
Набор полностью зелёный (55 to 0; 1713 pass + 4 skip). Всё тест-сторона:
- AuditChainRaceConditionTest: advisory-lock в audit_chain_hash РЕАЛЬНО присутствует
  (миграция 2026_05_30 применяется) — падало наблюдение: bind-параметр в SQL-сдвиге
  (? >> 32) не сдвигал → classid не совпадал. Декомпозицию ключа считаем в PHP.
  NB: db/schema.sql хранит функцию БЕЗ блокировки (минорный дрейф канона; прод через
  миграцию защищён) — стоит перегенерить schema.sql отдельно.
- SupplierConnectionTest WARN#2: matchEligibleProjects ограничен cap=LeadDistributor::CAP=3;
  ждать 3 из 6 видимых тенантов (кросс-tenant видимость под BYPASSRLS; при RLS было бы 0).
- SupplierWebhookTest + ValidationFormatTest: фаза 3 намеренно приняла проект без
  B-префикса как DIRECT (не теряем заявки) — тесты под новый контракт (202 / 422 по vid).
- SupplierPortalClientTest: fake-паттерн под старый URL /admin/rt-projects-load; клиент
  зовёт /admin/visit/rt-projects-load — обновлён паттерн.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:46:43 +03:00
Дмитрий 01daa529d7 test: дозакрытие оздоровления — protekateli pd-аудита, видимость supplier, новый флоу регистрации
Снижение остатка 19 to 5. Всё тест-сторона:
- PdErasureServiceTest + AdminPdSubjectRequestsControllerTest: SharesSupplierPdo —
  перестали коммитить pd_processing_log через pgsql_supplier, что ломало
  глобальный audit:verify-chains (6 падений) и амплифицировало PhoneRegionSmoke.
- ReportFileDeletePdLogTest: SharesSupplierPdo — cron reports:cleanup-expired
  теперь видит незакоммиченные job'ы теста.
- AdminSuppliersControllerTest: устойчивый ассерт (с фазы 3 в suppliers есть direct).
- AuthLogCoverageTest/AuthFlowIntegrationTest: новый флоу самозаписи G1/SP1 —
  register_success пишется после confirm-email; добавлен шаг подтверждения.
- ImpersonationTest end: verify (G7-B) ставит маркер impersonation → admin-зона
  закрыта by design; помечаем токен used напрямую вместо session-takeover.
- CleanupInactiveSupplierProjectsJobTest: phase A читает pivot project_supplier_links —
  добавлена привязка linkProjectToSupplier (раньше был только legacy FK).
- Pint-нормализация uses() FQN to import в ранее тронутых файлах.

Остаток 5 (НЕ слепой патч): webhook B-префикс ×2 (решение владельца), advisory-lock
audit-цепочки (возможный дрейф схемы, флажок), SupplierConnection WARN#2 (cap-3,
поведенческое), SupplierPortalClientTest (пре-существующий, не от этих правок).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:19:53 +03:00
Дмитрий 466d40e59a test: оздоровление тест-стенда — изоляция протекателей плюс фикстуры, партиции, видимость supplier-коннекта
Закрыто 36 из 55 пре-существующих падений backend-набора (55 to 19), всё тест-сторона,
код продукта не тронут. Группы:
- incident-показ/РКН: добавлен SharesSupplierPdo + синхрон уровня транзакции в трейте
  (вложенный transaction на общем PDO теперь делает SAVEPOINT, не повторный BEGIN).
- auto-pause и lead-delivery: тесты создают project_routing_snapshots, от которого
  зависит выбор кандидатов в LeadRouter (slepok-инвариант).
- изоляция 16 протекающих тестов: добавлен DatabaseTransactions (где нужно плюс
  SharesSupplierPdo) — перестали оставлять committed-строки, отравлявшие глобально
  сканирующие тесты (snapshot, verify-audit, size-N).
- partition time-bombs: ensureRange месячных партиций для тестов на дату 2026-05.
- устаревшие ассерты: SchemaDelta метрики v8.35 to v8.52, ProjectsStore телефон 8 to 7
  нормализуется, incidents-watch фильтр активного admin, register captcha_token,
  impersonation активный юзер тенанта, activity_log.deal_id, ProjectUpdateDedup пауза.

Остаток 19 (отдельно): verify-audit-chains и size-N (протекатели audit-строк),
webhook B-префикс (решение владельца), пара env/каскадных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:39:51 +03:00
Дмитрий bd18cfbff0 docs: прогон тупой пользователь 24.06 плюс находки и упрощения
Спека, план-ранбук и папка находок прогона портала глазами тупого
пользователя: REPORT, SIMPLIFICATION, A11Y, SESSION-HANDOFF и 26 скриншотов
включая FIX-снимки U1 и B1. Тестовый телефон в A11Y замаскирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:21:57 +03:00
Дмитрий c71c7df3ba test: капча-драйвер null в тестах плюс изоляция migrate и billing-summary от состояния БД
Капча: добавлен тестовый драйвер CAPTCHA_DRIVER=null и CAPTCHA_FAKE_PASSES
в phpunit.xml, иначе тесты наследовали боевой yandex из .env и регистрация
падала на проверке я не робот. Плюс изоляция двух тестов от состояния
учебной базы: BillingMigrateLeadsToRubTest считает операции своего тенанта,
BillingSummaryProviderTest берёт дату вне периода из следующего месяца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:20:14 +03:00
Дмитрий 142a561d0d fix/dashboard: хватит на N дней от дневного заказа B1 синхронно с биллингом
B1 из прогона тупой пользователь 24.06: RunwayCalculator считал от расхода
за 30 дней, из-за чего на дашборде показывал 0 дней при полном балансе и
раздувал число на короткой истории. Теперь считает от дневного заказа
активных проектов requiredLeadsForTomorrow, как витрина ёмкости биллинга,
и дашборд совпадает с биллингом. Тесты переписаны под новое поведение.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:19:41 +03:00
Дмитрий 82ddb762d9 fix/projects: понятные подписи источника вместо домен конкурента и донор
U1 из прогона тупой пользователь 24.06: site/call поля формы проекта
получили человеческие подписи и подсказки вместо жаргона донор/конкурент.
Затронуты NewProjectDialog.vue и ProjectDetailsDrawer.vue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:16:11 +03:00
Дмитрий 972000b03d fix: синхронизация deploy/redeploy.sh с прод-фиксом sudo chmod + .gitattributes LF для .sh 2026-06-24 16:54:45 +03:00
Дмитрий dd9b52b587 fix/onboarding: косяк 07 §6 — мелочи (имя «Новый», индикатор пароля, 401 /me)
1) Регистрация больше не подставляет фейковое «Новый клиент» — first/last_name
   пустые, приветствие падает на «коллега», аватар/имя из email; настоящее имя
   клиент укажет в профиле.
2) Индикатор силы пароля: частые/словарные пароли (password123, qwerty, 123456…)
   теперь «Слабый» независимо от длины/цифр (vitest RED→GREEN).
3) На гостевых страницах при холодном старте не дёргаем /api/auth/me — убран
   401-шум в консоли у неавторизованного посетителя; восстановление сессии на
   защищённых роутах сохранено.
Проверено глазами на 8000 (имя «коллега», 0 ошибок в консоли логина) + vitest.
Капча под нагрузкой/headless (§6.4) — заметка для приёмки, отдельно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:48:35 +03:00
Дмитрий b52adb64d5 fix: устаревший support@liderra.app заменён на support@liderra.ru в env-шаблоне и тесте 2026-06-24 16:40:57 +03:00
Дмитрий e3f90d78f9 fix/onboarding: косяк 07 — стартовый блок на дашборде + чистый пустой список проектов
Шаг 1: на дашборде у новичка без активных проектов и без лидов показываем
блок «Добро пожаловать» с шагами и кнопками «Создать первый проект» / «Пополнить
баланс» (скрываемый, помнит dismiss в localStorage). Честные нули не подменяем.
Шаг 2: при пустом аккаунте (0 проектов и фильтры не сужают) на /projects прячем
баннер «до 18:00», фильтры и пагинацию — остаётся заголовок, «Создать проект»
и подсказка пустого состояния.
vitest: онбординг видим/скрыт/dismiss + пустой аккаунт скрывает шум; зелёные.
Проверено глазами на 8000. Предсуществующие фейлы (drawer-integration,
runway «28 дня») не мои — падают и на чистом коде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:35:20 +03:00
Дмитрий 2fbc612827 fix/projects: косяк 06 — окно перегрузки на «вы» + кнопка «Пополнить баланс»
Единственное место с обращением на «ты» переведено на «вы» (У вас…,
пополните/поставьте/уменьшите). В окно добавлена кнопка «Пополнить баланс»,
которая закрывает окно и ведёт в /billing (онлайн-оплата ждёт Б-1, но вход
в пополнение теперь под рукой). Логику/цифры не трогал.
vitest: текст-на-вы + переход в /billing, 7 passed. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:20:59 +03:00
Дмитрий 53482b91c3 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
Дмитрий 0c65c79cc6 fix/projects: косяк 04 — реквизиты первым шагом визарда создания (вариант C)
Новый клиент без реквизитов заполняет короткие реквизиты прямо в окне
создания проекта (шаг 1: тип лица, имя, телефон, ИНН для юр/ИП), затем
переходит к форме проекта (шаг 2) — без ухода на /settings и без потери
черновика. Решение по шагу — по light-complete реквизитов (fail-open при
ошибке чтения; бэкенд-гейт 422 остаётся запасным путём, тоже инлайн).
Убран старый алерт с уходом на /settings. Vitest 20 passed, оба пути
проверены глазами на 8000 (есть реквизиты сразу проект; нет шаг 1 затем 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:07:02 +03:00
Дмитрий ae5659bab1 fix/projects: косяк 03 — Вся РФ одной галочкой, галочка сразу снимает ошибку регионов
Галочка Вся РФ теперь подтверждает выбор одним кликом и сразу очищает
зависшую ошибку regions, убрана отдельная кнопка Подтверждаю и предупреждение.
Защита от случайной всей РФ сохранена осознанным кликом галочки.
Drawer пусто=вся РФ не трогал. Vitest RED→GREEN на старом коде доказан,
NewProjectDialog specs 16 passed. Проверено глазами на 8000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 13:31:10 +03:00
Дмитрий 879c2e7142 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
Дмитрий 6b67ecab63 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
Дмитрий 99aa864800 feat: публичные юр-страницы оферта/политика/возврат + реквизиты + страница цен под модерацию ЮKassa 2026-06-24 09:31:52 +03:00
Дмитрий 6e3424165f docs: ранбук включения онлайн-оплаты ЮKassa на ИП для прода liderra.ru 2026-06-24 08:02:04 +03:00
Дмитрий efd8a1dbd0 docs: отложенные спека и план UX-блокировки смены источника проекта 2026-06-24 05:57:16 +03:00
Дмитрий edd6e297f2 docs: хэндофф сессии деплоя 23-24.06 + промт следующей сессии
Что сделано стек на проде 29781fb9 деньги клиента целы фикс redeploy.sh
пред-деплойный аудит омега разобрана до конца. Что осталось омега-решение
владельца плюс Б-1 ООО плюс мелкая уборка. Грабли прода и промт для новой
сессии внутри. Телефоны омеги замаскированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:48:37 +03:00
Дмитрий 29781fb913 deploy: выкат стека на прод 23.06 + фикс redeploy.sh composer-прав + снимок ПИЛОТ
Выкачен недеплоенный стек becff632 балансовый блок плюс F/J плюс source-edit-lock
плюс ЮKassa спит флаг off. Миграция add_balance_transaction_id применена вручную
под postgres таблица crm_migrator-owned. Деньги клиента целы tenant 2
1836400 руб 1013 сделок эталон до==после. Квирк-107 ок очередь active HTTP 200.

Фикс redeploy.sh композер install от root COMPOSER_ALLOW_SUPERUSER плюс chown
vendor www-data баг положил прод в 500 на окно наката. README дополнен граблями
composer-прав opcache-рестарт и crm_migrator-миграций. Снимок ПИЛОТ обновлён.
cspell-words пополнен проектным жаргоном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:50:40 +03:00
Дмитрий becff6327b chore/schema: сверка-чистка перед запуском — метрика RLS 42 в 44 + пред-деплойный аудит
Сверка боевого read-only перед накатом: прод-код == git побайтово 0 правок
мимо git, вся работа всех сессий включая оплату в main+gitea, прод-БД == канон.
Единственная нечистота — счётчик RLS-политик в шапке schema.sql отставал
42 реально 44 == прод pg_policies. Поправлено структурно БД не менялась плюс
запись в CHANGELOG_schema. Полный аудит дрейф+потери — отдельной запиской.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:17:36 +03:00
Дмитрий acebfddd25 chore: закрыть мелочи докалки + отметить прод-ops done 23.06
deptrac закрыт через design-механизм самого конфига заведён
app/deptrac.baseline.yaml подключён через imports фиксирует
ProjectResource SupplierSnapshotGuard read-only расчёт замка для UI
прогон 0 violations 1 skipped гейт ловит только новый дрейф.

larastan принят env-факт ide-helper L13 Windows на CI зелено не код-дефект.
Диагностируемость молчаливого дропа won't-fix warning спамил бы на каждый
лид намеренно-выключенного проекта.

Прод-ops отмечены сделанными failed_jobs очищены 494к до 7 tenant 24
удалён soft-delete 22.06 проверено живьём 23.06.

Отметки в хэндоффах FJ и сводном чтобы перестало таскаться.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:24:12 +03:00
Дмитрий 926f358105 docs: поправка по лидам + сверка приёмки раунд-2 + статусы долгов 23.06
Лиды не идут с 3 июня = намеренное решение владельца он сам выключил
проекты подтверждено 23.06 не дефект. Развёрнута вчерашняя поправка
ушедшая в крайность реальный инцидент. Новый файл-разбор
2026-06-23-LEADS-STOPPED-correction.md с доказательствами прода
read-only SSH плюс баннеры во всех связанных доках. Телефон-источник
замаскирован.

Приёмка раунд-2 сверена живьём все 5 закрыты M-1 M-2 apiv1-rate на проде
hash 21 schema v8.51 Раздел B тесты.

Долги докалки проверены 23.06 всё ещё открыты deptrac 1 живое нарушение
ProjectResource SupplierSnapshotGuard larastan env-квирк диагностируемость
молчаливого дропа no_snapshot_skipped всё ещё Log info.

Только docs прод и код не тронуты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:51:55 +03:00
Дмитрий 681e6ca631 feat/billing: F/J — единый расчёт замков + пополнение/пересчёт снимают оба замка
- 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
Дмитрий 5275f93f42 docs: сводный хэндофф сессии 23.06 + промт следующей сессии (балансовый блок + хвосты 2 дней)
Фиксирует: сделанное в сессии (N/G/E/H/M/O закрыты, gitea 8eef54ac..696e75b9,
не на проде); остаток балансового блока (L/F/J); долг larastan(env)/deptrac;
сводку хвостов 22-23.06 (недеплоенный стек ~99 коммитов, FN-RESET/FN-SESSION,
failed_jobs, G5/ООО); открытые вопросы владельцу; готовый промт для next-session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:22:48 +03:00
Дмитрий 4a71ce655d docs: обнови ПИЛОТ — снимок 23.06 (балансовый блок N/G/E/H/M/O на gitea, прод не менялся)
Балансовый блок проектов закрыт 6 находками (N/G/E/H/M/O), по TDD+визуал,
gitea/main 8eef54ac..696e75b9, на прод НЕ катилось. E подтверждён живым
заказом в кабинете поставщика + уборка. L (омега tenant 25/проект 188)
остаётся открытым — сверка после деплоя + GO. Также зафиксирован долг
larastan(env-косяк ide-helper)/deptrac(ProjectResource->SupplierSnapshotGuard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:13:02 +03:00
Дмитрий 696e75b9ac fix/projects: O — панель правки показывает блок при 409 (не молчит)
Боковая панель ProjectDetailsDrawer ловила в onSave только 422; при 409
balance_insufficient (лимит превышает баланс) ничего не показывала — клиент
не понимал, почему правка лимита не сохранилась. Теперь под полем лимита
выводится причина с ёмкостью и запрошенным объёмом + призыв пополнить баланс.

TDD: ProjectDetailsDrawer.spec — 409 balance_insufficient показывает сообщение,
saved не эмитится. Глаза: лимит 100 при ёмкости 60 → PATCH 409 → видимое
«Лимит превышает баланс: хватает на 60 лид(ов), запрошено 105…».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:44:06 +03:00
Дмитрий 13167f668c fix/projects: M — правка site/call проекта больше не падает молчаливым 422
Диалог «Редактировать» делал Object.assign(form, project) и слал весь объект,
включая sms_senders=null для site/call. UpdateProjectRequest валидирует
sms_senders как sometimes|array|min:1 → present-null проваливал array → 422.
Поле sms на форме site/call не отрисовано, поэтому errors.sms_senders некуда
показать — диалог «висел молча». Теперь persist() для не-sms проектов не шлёт
sms_senders/sms_keyword (заодно не триггерит зря snapshot-guard).

TDD: NewProjectDialog.spec — правка site не содержит sms-полей в PATCH body.
Глаза: правка site-проекта → PATCH 200, имя изменилось, диалог закрылся.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:39:14 +03:00
Дмитрий 18ce733f62 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
Дмитрий c018ea4e73 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
Дмитрий b2d334e782 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
Дмитрий 8eef54acda 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
Дмитрий 403c779110 feat/billing: blocked project не уходит к поставщику при создании + разбор и план
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:30:27 +03:00
Дмитрий e672c8b3f9 fix(billing-security): хардненинг webhook ЮKassa + чистка admin-auth комментариев
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
Дмитрий cafc6950fe Merge branch 'feat/source-edit-lock-ux' 2026-06-23 03:48:18 +03:00
Дмитрий d366be021f fix(tests): обновить Kanban/BalanceCapacityIndicator под правки этой сессии
KanbanView: удалён тест клика по убранной кнопке «Новая сделка».
BalanceCapacityIndicator: Vuetify-плагин в mount (v-icon) + формат «1 000 ₽».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 03:47:19 +03:00
Дмитрий 075d12bef3 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
Дмитрий 8c37b44c47 style(billing): pint — импорты в AppServiceProvider для биндинга драйвера 2026-06-22 21:53:19 +03:00
Дмитрий 178dd33ea8 feat(billing): фронт — редирект на оплату при confirmation_url + баннер возврата
TopupResult допускает confirmation_url; TopupDialog при нём редиректит на
страницу ЮKassa (через тестируемый redirectTo), иначе прежнее мгновенное
зачисление. BillingView показывает баннер «платёж обрабатывается» при
возврате ?topup=return. Пресеты сумм уже были.
2026-06-22 21:50:13 +03:00
Дмитрий 9b1fd3160a feat(billing): админ-эндпоинт ввода ключей ЮKassa с шифрованием config 2026-06-22 21:45:26 +03:00
Дмитрий a051c2c4eb 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
Дмитрий 049dbb9954 feat(billing): развилка topup по флагу billing_yookassa_enabled — шлюз vs заглушка
Флаг ВКЛ → создание платежа через OnlineTopupService + confirmation_url;
ВЫКЛ → прежнее мгновенное зачисление. Биндинг PaymentGatewayDriver в
AppServiceProvider. Также мелкая гигиена SystemSettingsHelperTest
(DatabaseTransactions для отката).
2026-06-22 21:36:30 +03:00
Дмитрий f40a0233ff feat(billing): PaymentGatewayManager + OnlineTopupService — создание онлайн-платежа 2026-06-22 21:08:46 +03:00
Дмитрий 49bbabcb3f feat(projects): блокировка полей источника + подсказка в drawer, баннер нового проекта
ProjectDetailsDrawer: при source_locked поля источника disabled + подсказка
с датой; красный баннер прочих ошибок не тронут. NewProjectDialog: баннер
«первые лиды с DD» (правило слепка 18:00). Тексты согласованы с владельцем.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:59:02 +03:00
Дмитрий a1ad4da626 feat(projects): leadDate util + source-lock поля в типе Project
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:54:28 +03:00