Commit Graph

44 Commits

Author SHA1 Message Date
Дмитрий 42e907c882 Merge gitea/main into feat/sales-finder — сведение с боевым перед выкатом
Ветка разошлась с боевым 28.06 (334 коммита в main / 215 у нас). Влито ВСЁ боевое:
автоподбор конкурентов, мобильный адаптив портала, свой учёт посетителей, мониторинг
внешних сервисов, фиксы поставщика/биллинга/бота/разборов.

ПОБАЙТОВАЯ СВЕРКА: из 1008 файлов, изменённых боевым, 989 совпадают точно;
19 отличаются — все с нашей законной работой (обе стороны внутри). Затёртых — 0.

24 конфликта разобраны вручную. Ключевое:
- VerifySupplierOrderJob — взята БОЕВАЯ версия (фикс инцидента 11-12.07: площадка
  берётся из src, а не из имени; наша была старой и вернула бы баг, терявший заявки).
- SyncSupplierProjectsJobTest — 15 боевых тестов + наш уникальный (limit-1 → только B1).
- routes/web, router/index, config/services, bootstrap/app — обе стороны сложены.
- NewProjectDialog — зелёные дни недели (наше) + мобильная раскладка (боевое).
- CHANGELOG схемы — номера версий столкнулись, наши перенумерованы в v8.67-v8.70.
- composer — обе зависимости (laravel-dompdf наш + geoip2 боевой).

Гейты: бэкенд 2907/2911 (0 падений), Larastan 0, фронт 1333/1333, сборка OK.
Baseline статанализа принял пре-существующий долг боевого кода (автоподбор/чат).
@mixin в 26 моделях — требование статанализа, dev-докблок, на рантайм не влияет.

Откат: git reset --hard pre-merge-main-20260714

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:49:28 +03:00
Дмитрий 9233bf9960 Merge remote-tracking branch 'gitea/main' into worktree-jivo-bot-core
# Conflicts:
#	app/routes/console.php
2026-07-14 13:27:46 +03:00
Дмитрий c0487ddaa5 fix(billing,ux): портал видит отмену платежа и перестаёт молчать в формах
Разбор живого клиента (стоматология, Красноярск, 14.07): 2 часа настраивал 26
проектов, получил 14 отказов в трёх формах и ушёл, не заплатив.

Деньги:
- отменённый шлюзом платёж больше не висит «ожидает» вечно: закрываем как failed
  с причиной (PaymentSettlementService — общий путь для webhook и крона);
- billing:reconcile-payments каждые 5 минут сам спрашивает шлюз про зависшие
  pending. Побочно страхует от ПОТЕРИ ДЕНЕГ: если webhook не дойдёт, оплаченный
  платёж всё равно зачислится;
- кабинет говорит правду: «Оплата не завершена» + «Оплатить снова» вместо
  «баланс обновится автоматически» (GET /api/billing/last-payment).

🔴 RLS-мина (поймана валидатором ДО выката): UPDATE при отмене шёл без
tenant-контекста → на проде тронул бы 0 строк, а портал рапортовал бы «отменено».
Тесты слепы (тестовая БД под postgres). Регресс-тест проверяет ПОРЯДОК:
SET LOCAL tenant ДО UPDATE. Тот же класс, что инциденты 07.07 и 12.07.

Формы (клиент бился и уходил):
- удаление проекта со сделками: причина показывается на месте + кнопка
  «Поставить на паузу» (раньше 422 улетал в никуда — 4 попытки впустую);
- создание проекта: ошибка по дням недели больше не молчит (у поля не было
  места для показа — 2 немых отказа);
- автоподбор «Добавить вручную»: показываем причину от сервера (был голый
  catch {}), длинные ссылки 2ГИС/Яндекс.Карт принимаются — трекинг-хвост срезаем
  сами. Воспроизведено тестом: именно длинная ссылка давала 3 отказа подряд.

Наблюдаемость: причины отказов пишутся в журнал (маршрут, tenant, ИМЕНА полей;
значений нет — 152-ФЗ). Уровень warning: на проде LOG_LEVEL=warning, info в
журнал не попадает вовсе. Робот-сверщик добавлен в реестр пульса.

Тесты: Pest 2475/2475, Vitest 1215/1215.
Выкачено на боевой 14.07.2026 ~13:00 МСК; сверка сразу закрыла 3 мёртвых платежа
(10 000 ₽, 5 000 ₽, 1 000 ₽).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 13:14:49 +03:00
Дмитрий 9ff19f3cc3 @
feat(bot): всеобъемлющая инструкция (36 статей) + 31 узкая экскурсия, которая открывает окна

Живой урок владельца 12.07.2026: «дубли чистятся в 2-х местах, а бот выдумал третье»,
«стыдно такого бота показывать», «видео показываем полное, а я спрашиваю про одно —
надо резать», «надо не только кнопку показывать, а и что за ней».

База знаний: 17 обзорных статей → 36 узких по темам. Портал обойдён целиком
(5 агентов): все экраны, окна, подсказки «(?)», тексты ошибок, внутренние правила.
Новое, чего бот не знал: дубли в ДВУХ местах (Поле и Предложения) + окно «источник
уже есть»; правило 18:00; почему приходит меньше лимита; оплата по счёту и акты;
списания; безопасность и 2FA; отчёты; массовые действия; регистрация.

Экскурсии: 15 → 31, каждая на свою тему (3–7 шагов). Раннер GuidedTour научен
шагу `open` — сам открывает окно/вкладку и подсвечивает то, что ВНУТРИ
(окно дублей, форму подбора, диалог пополнения, панель тарифов, карточку сделки).
99 новых якорей data-tour расставлено по всему порталу (4 агента).

Поиск: веса в tsvector (заголовок A / синонимы B / текст C) — иначе на «чистка
дублей» первой всплывала статья «Списания» из-за мимоходного упоминания «поля».

Тесты: Pest бот 39/39, Vitest 1222/1222. Новые сторожа: у каждой статьи есть
экскурсия; экскурсия не короче 3 и не длиннее 7 шагов; каждая цель экскурсии
реально существует в разметке; экскурсии про действия обязаны открывать окно.
Живая проверка в браузере: экскурсия про дубли сама открыла окно объединения
и перешла на второе место чистки.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@
2026-07-12 12:56:22 +03:00
Дмитрий 561096cc84 feat(tours): 15 полноценных экскурсий вместо 6 обрубленных + раннер водит по экранам
Претензия владельца 12.07.2026: «видео мало и они обрубленные».

Причина «обрубленности» найдена живым прогоном: раннер НЕ переключал экраны —
шаг про «Проекты» показывался поверх «Биллинга». Фикс GuidedTour: при переходе
на шаг с другим route зовём router.push (TDD-тест «шаг на другом экране»).

Каталог: 6 → 15 экскурсий, все минимум по 3 шага (тест-сторож в TourCatalog.spec):
start (4 шага), create-project (6), top-up-balance (6), tariffs (4), balance-pause (4),
deals (4), kanban (3), konkurentnoe-pole (6), change-source (4), security (4),
notifications (3), import (4), api (3), dashboard (4), support (3).

Якоря data-tour расставлены по портале (3 агента): биллинг (кошелёк, запас,
ступени, вкладки, сумма, способ оплаты), проекты (фильтры, карточка, меню, тип
источника, лимит, регионы, дни), сделки (фильтры, таблица, экспорт), канбан,
импорт, дашборд, настройки (вкладки, 2FA, матрица уведомлений), Поле (собрать,
вручную, дубли, вкладки, карточка, легенда, изучение), помощь (FAQ, форма).

Каждая из 17 статей инструкции получила свою экскурсию (frontmatter tour:).

Vitest 1216/1216, Pest бот 46/46, живая проверка в браузере: экскурсия сама
переходит между экранами и подсвечивает цели.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:48:46 +03:00
Дмитрий af2c4cbd15 fix(portal-mobile): 4 фикса вёрстки на телефоне (обход всех экранов)
Прошёл все экраны портала на 390px, нашёл и починил переполнения:

1. NewProjectDialog: кнопки дней недели (ПН…ВС) в окне редактирования
   проекта не влезали — СБ и ВС уезжали за правый край и были не видны.
   На телефоне делим ряд поровну между 7 кнопками.

2. DaysBulkDialog (массовое «Дни сбора»): та же беда с двумя рядами дней —
   тот же приём (flex 1/1/0 на телефоне).

3. ApiTab: пары кнопок «Копировать+Перегенерировать» и
   «Сохранить+Тестовый webhook» не помещались в строку — длинная вторая
   кнопка обрезалась. Разрешаем перенос на телефоне (flex-wrap).

4. NotificationsTab: таблица «События × каналы» была шире экрана —
   колонка Email уезжала, а названия событий крошились по одному слову.
   На телефоне ужимаем колонки-галочки и уменьшаем отступы, событию
   достаётся почти вся ширина; заголовки каналов мельче.

Все правки — только в phone-media (<600px), десктоп не затронут.
Проверено замерами DOM + скриншотами на 390px; тесты затронутых
компонентов зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:14:15 +03:00
Дмитрий cdd0237108 fix(портал-мобилка): диалоги на телефоне — на весь экран (fullscreen)
Узкие центрированные модалки на телефоне резали содержимое (ряды кнопок,
вкладки, дни недели, сетку регионов — контент вылезал за края, текст обрезался
слева и справа). Диалоги с богатым содержимым теперь на телефоне открываются
на весь экран (:fullscreen по useLayoutTier + scrollable):
- Проект (создание/редактирование) — NewProjectDialog
- Новая сделка — NewDealDialog
- Пополнение баланса — TopupDialog
- Дни оптом — DaysBulkDialog (те же кнопки дней)
- Регионы оптом — RegionsBulkDialog
- Импорт: сопоставление статусов — UnknownStatusesDialog
Десктоп/планшет — прежний центрированный модал, без изменений.
Тесты диалогов 59/59 (+2 skip) зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:46:47 +03:00
Дмитрий b0bd110f8f fix(projects): регионы в 4 выпадашках раздела «Проекты» — сортировка по сути названия
Те же выпадашки региона (новый проект, фильтр списка, массовое
add/remove регионов, карточка проекта) переведены на REGIONS_ALPHA —
единый порядок с автоподбором, республики по своему имени, а не кучей.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:30:42 +03:00
Дмитрий d15a7cfd93 feat(биллинг-фронт): баланс на запуске — сообщения в рублях, метка «не запущен», группы
Фронт для правила «баланс блокирует запуск, а не создание»:
- ProjectLimitOverloadDialog переформулирован в «Проект не запущен — пополните
  ~X ₽ или уменьшите объём» (сумма в рублях topup_rub).
- NewProjectDialog: создание всегда успешно; при launch.deferred показывает
  сообщение (не блок). Update-лимита 409 → тот же диалог.
- Возобновление (toggle-active) ловит 409 balance_insufficient (стор не
  переключает is_active), показывает диалог; постоянная метка «Не запущен —
  не хватает баланса» на карточке/в панели по preflight_blocked_at/balance_blocked.
- BulkActionsBar: сводка «Запущено N, отложено M — не хватает баланса» на resume.
- Автоподбор: CreateScreen показывает сводку запуска (рубли); FieldCompetitor
  массовые pause/resume через общий POST /api/projects/bulk (наследует BULK_MAX,
  слепок-защиту, балансовый гейт).
- ProjectResource отдаёт preflight_blocked_at.

Новые/обновлённые фронт-тесты зелёные. Пред-существующий красный baseline
фронт-набора (ErrorView/Settings/Legal/… + ProjectsView v-show) не трогался.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:04:16 +03:00
Дмитрий a0caeb43e1 feat(ui): тип лица полными словами, зелёные дни недели, скрытие банк-реквизитов у физлица
- Настройки→Реквизиты и диалог проекта: «Физлицо»→«Физическое лицо», «Юрлицо»→«Юридическое лицо» (ключи value не тронуты)
- У физлица скрыт блок «Реквизиты для оплаты» (банковских реквизитов нет; оплата по счёту — только для юр/ИП)
- Диалог проекта: выбранные дни недели залиты зелёным #0f6e56 как в ProjectDetailsDrawer

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 13:22:09 +03:00
Дмитрий b4fbff032b feat: подсказка «Как увеличить количество сделок» в диалоге проекта
Над «Откуда собирать заявки» добавлена строка-подсказка с tooltip:
лимит распределяется по поставщикам равномерно; даже если не выбирается
полностью — просто увеличить лимит, и сделок придёт больше.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 13:35:29 +03:00
Дмитрий ddeec73c94 feat: подсказка «Как увеличить количество сделок» в диалоге проекта
Над «Откуда собирать заявки» добавлена строка-подсказка с tooltip:
лимит распределяется по поставщикам равномерно; даже если не выбирается
полностью — просто увеличить лимит, и сделок придёт больше.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 13:32:09 +03:00
Дмитрий aa0d595f67 refactor(fe): снять ручные обходы меню - заменены глобальным установщиком
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:27:46 +03:00
Дмитрий 2ce16436e0 fix/ui: объявления о датах — актуальны по времени суток (правило 18:00/21:00 МСК)
Найдено проверкой глазами в 19:27 МСК (после 18:00):
1. Баннер правок количества/региона/дней говорил «вступят со следующего дня» — врал
   после 18:00 (правка не попадает в сегодняшний слепок → реально послезавтра). Теперь
   показывает АКТУАЛЬНУЮ дату через firstLeadDate (до 18:00 → завтра, после → послезавтра):
   «…вступят в силу с 27 июня». Дроуэр + окно «Редактировать».
2. Сообщение блокировки удаления/смены источника в SupplierSnapshotGuard было захардкожено
   «мы увидим это сегодня в 18:00 … можно будет послезавтра» — после 18:00 «сегодня в 18:00»
   уже прошло. Теперь time-aware через computeGraceUntil: «…лиды придут до 26 июня … можно
   будет после 26 июня».

Проверено глазами: баннер лимита (27 июня), подтверждение источника (до 26 июня),
блок удаления (после 26 июня) — все согласованы и меняются по времени суток. Тесты:
guard 30/30, фронт 38/38, leadDate (18:00 порог) зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:40:04 +03:00
Дмитрий de4872a6b3 feat/ui: текст правила смены источника — единый из API, без дублей в JS (Эпик 6.3)
ProjectResource.source_change_message = ProjectRuleMessages.sourceChanged (тот же текст,
что in-app уведомление 6.2). Диалоги подтверждения (дроуэр + окно Редактировать) тянут его
из API с fallback на локальный текст. Бэкенд — единственный источник строк правил, экран и
колокольчик не расходятся. Проверено глазами (epic6-unified-rule-text-confirm.png). Тесты:
ProjectResource 5/5, дроуэр 27/27, EditProjectDialog 7/7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:08:47 +03:00
Дмитрий 5d79a4530c feat/ui: окно «Редактировать» — источник редактируем + объявление + подтверждение (Эпик 3)
Найдено при проверке глазами: «Редактировать» открывает NewProjectDialog (mode=edit),
где источник был :readonly — отдельный от дроуэра путь без UX замка. Привёл к тому же
поведению Эпика 3:
- источник site/call больше не readonly в edit;
- баннер np-applies-from-banner при правке количества/региона/дней на залоченном проекте;
- диалог np-source-change-confirm при смене источника, PATCH только после подтверждения.
Оба окна (панель справа + «Редактировать») теперь консистентны. Проверено в браузере.
EditProjectDialog 7/7, NewProjectDialog регрессия 20/22 зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:28:14 +03:00
Дмитрий f6c002b3db feat/ui: подсказки вопросик по карте — дашборд, мастер проекта, биллинг
Дашборд: вопросик у KPI Получено/Конверсия/Активные, у воронки и у прогноза хватит на N дней; pp на п.п.
Мастер проекта: подпись лимита заявок в день + вопросик у выбора источника Сайт/Звонок/СМС.
Биллинг: вопросик у Цены за лид 7 ступеней.
Тест FunnelChart 8/8, type-check чистый, затронутые спеки 58/60.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:41:27 +03:00
Дмитрий d8f13cbe87 fix/ui: убрать жаргон в клиентском UI — без конкурент/синхронизация/crm.bp-gr.ru/Pay-per-lead
U1 остаток: пояснения источника проекта без слова конкурент.
U5: баннер списка проектов про результат а не синхронизацию + статус Собирает заявки.
Деалы/импорт/логин: убрано имя поставщика и англ Pay-per-lead.
Активность сделки: технический supplier_webhook заменён понятной фразой.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:02:52 +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
Дмитрий 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
Дмитрий 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
Дмитрий 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
Дмитрий ada7088511 feat(projects): клиентская валидация формы нового проекта (F-NEWPROJECT-1) + синк статусов находок по проду
- NewProjectDialog.submit(): обязательны название и источник (домен/номер/отправители по типу сигнала); ошибка показывается сразу, без обращения к серверу. +2 TDD-теста, два существующих теста обновлены под новый гейт. Полный фронт-сьют — без новых падений; проверено глазами в браузере на локалке.
- docs: пометки находок приёмки/UI/impersonation/ADR-018 приведены к фактическому прод-статусу (M-1, M-2 капча Yandex, FN-RESET/2/3/ENC, F-CSV, apiv1-rate, N-4, F-T1, F-P1 — на проде; failed_jobs очищены 494191->0; tenant 24 удалён soft-delete).
- решение владельца 22.06: admin-area доделки (saas-admin SSO, two-person approval, role-guard супер-админа, supplier fallback, обезличенный admin_user_id) сняты как отдельные задачи — доделать единым пакетом вместе с подключением Yandex SSO после ООО.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:43:10 +03:00
Дмитрий 8372cd497d fix(ui): dev-индексы не текут в прод-сборку (гейт по command) + двойной плюс/очистка региона/перенос кнопок попапа 2026-06-21 12:16:45 +03:00
Дмитрий 6dd9f041fa feat(G1/SP3b): UX гейта — alert requisites_required + переход к реквизитам
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:24:27 +03:00
Дмитрий ae286b9a0d WIP чекпойнт: lead-region/supplier бэкенд + фронт-редизайн + Pint + тесты
92 файла одной пачкой. Исключены чужие зоны: CLAUDE.md, .claude/settings.json, docs/observer/.pii-counters.json.
gitleaks staged: no leaks found. Не верифицировано тестами - сохранение труда в историю.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 05:17:12 +03:00
Дмитрий fda7ed9148 feat(slepok): Task 2.11 UI — applies_from toast «вступит в силу N.21:00 МСК»
После правки slepok-чувствительных полей проекта (regions / delivery_days_mask /
daily_limit_target / источник) backend возвращает ProjectResource.applies_from
= N.21:00 МСК (Task 2.11 backend slice, commit f9b904d4). Клиент Лидерры
теперь видит расширенный тост: «Сохранено. Изменения вступят в силу
DD.MM.YYYY в 21:00 МСК.» Когда правка не затронула slepok — обычное
«Сохранено.».

Изменения:
- composables/appliesFromMessage.ts — чистый форматтер (Moscow tz, не локаль клиента).
- ProjectDetailsDrawer / NewProjectDialog / EditProjectDialog — emit('saved', appliesFrom).
- ProjectsView — v-snackbar + onSaved/onDrawerSaved обработчики.
- tests/Frontend/appliesFromMessage.spec.ts — 5 invariant-кейсов.

Plan §Task 2.11 Step 5-6. Spec §4.2.5 UX block. R-15 + R-06..R-08 UX closure.
Vitest worktree-only 944/3sk GREEN, vue-tsc 3 pre-existing errors (вне диффа),
ESLint clean на затронутых файлах.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:52:42 +03:00
Дмитрий 04f8467cf7 feat(billing-v2-c): UI префлайт Task 1.10 — баннер заморозки, индикатор ёмкости, диалог перегрузки
Spec C §3.6/§6.2. Бэкенд: GET /api/billing/balance-status (frozen + capacity + required + дефицит ₽/leads), Pest 6. Фронт: BalanceFrozenBanner (в AppLayout, глобально), BalanceCapacityIndicator (в BillingView под балансом), ProjectLimitOverloadDialog (409-перехват в NewProjectDialog: save-blocked/set-zero), tenantStore + api getBalanceStatus. Vitest +18.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:39:21 +03:00
Дмитрий 36e7b54ab2 fix(projects): closable-chips на селекторах регионов — удаление по одному
Раньше чтобы убрать один регион из выбора, приходилось сбрасывать все
и выбирать заново. Добавлен closable-chips на v-autocomplete регионов в
трёх местах: карточка создания проекта (NewProjectDialog), панель
редактирования (ProjectDetailsDrawer) и массовое изменение регионов
(RegionsBulkDialog). Теперь у каждого чипа есть крестик.

Покрыто Vitest: closableChips=true на каждом селекторе.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:21:10 +03:00
Дмитрий f0ba84990e feat(projects-ui): replace archive with delete, drop archived filter
- Remove archived_at from Project interface; rename store.archive → store.del
- BulkActionsBar: archive button → delete (testid, icon, confirm text)
- ProjectCard: archive menu item → delete (emit + icon)
- ProjectDetailsDrawer: confirm text + store.del call
- ProjectsView: @delete binding, remove 'Архивные' status filter entry
- vuetify.ts: add mdi-delete → Trash2 mapping
- All specs/stories updated: archived_at removed, archive → del renamed
- New test: del() calls DELETE /api/projects/{id}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:37:26 +03:00
Дмитрий dfa46d411c feat(projects): require region + explicit «Вся РФ» with warning gate
План 4 Task 4 эпика project-migration-redesign.

- NewProjectDialog: отдельный чекбокс «Вся РФ» (89 субъектов в autocomplete
  без sentinel сохранены) + inline v-alert предупреждение + подтверждение.
- Взаимоисключение: выбор субъектов снимает «Вся РФ» и наоборот.
- Гейт submit: блок если ни субъектов, ни подтверждённой «Вся РФ»
  (errors.regions = «Выберите регион...»); «Вся РФ» -> regions=[] на API.
- Лейбл autocomplete «Регионы» (убрано «(пусто = вся РФ)»).
- watch immediate:true — инициализация vsyaRf/edit-prefill при mount
  (чинит EditProjectDialog submit при модальном открытии).
- Vitest 3/3 новых + 22 passed соседних (NewProject/Edit/ProjectsView) без регрессий.
2026-05-20 14:34:27 +03:00
Дмитрий d064f18830 feat(projects/new-dialog): подпись «Источник» над полями на 3 табах
UX-request 18.05.2026 (п.8):
- Сайт: «Источник — домен сайта-«донора», с которого приходят лиды»
- Звонок: «Источник — телефонный номер «донора», на который звонят клиенты»
- СМС: «Источник — отправитель SMS и (опционально) ключевое слово в тексте»

Подпись text-caption text-medium-emphasis, выше существующего label поля.
Один и тот же NewProjectDialog используется и для create, и для edit.

NewProjectDialog.spec.ts 5/2sk/0 — без регрессий. Build 1.96s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:36:09 +03:00
Дмитрий 2c90349fb3 feat(projects): RegionsBulkDialog — subject-level regions (89 RF subjects) #1426
Bulk regions dialog reworked from federal-district bitmask to subject/region
selection, consistent with ProjectDetailsDrawer/NewProjectDialog. Full-stack:
add_regions/remove_regions on projects.regions INT[], BulkProjectActionRequest
split validation, ProjectService model-instance update. federal-districts.ts
removed (zero consumers). +menuRepositionFix util for v-autocomplete menu.
phpstan-baseline: bump actingAs ignore count 14->15 (new validation test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 03:41:46 +03:00
Дмитрий 0c96d96784 feat(ui): subject-level regions autocomplete in NewProjectDialog + PDD (Plan 6 Task 5)
- projectsStore: Project.regions?: number[] interface field
- NewProjectDialog: replace interim placeholder с v-autocomplete (89
  subjects + federal district subtitle); form drops region_mask/region_mode
  (backend dual-writes)
- ProjectDetailsDrawer: replace maskToCodes/encode-watch с direct
  form.regions binding; same v-autocomplete component
- Vitest: +2 NewProjectDialog tests (count=89, POST payload includes regions[]);
  refactor 3 existing PDD region tests на regions[] model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:54:05 +03:00
Дмитрий 7429ee4185 fix(projects): #909 СОЗДАТЬ кнопка — apiClient + interim A regions
Root causes:
1. Default axios без withXSRFToken не отправлял CSRF header → 419 silent
   fail (catch ловил только 422).
2. PDD regions UI (commits d0ee2d8..0e5e5a9) использовал 32-bit маску,
   несовместимую с schema's 8-битным CHECK chk_projects_region_mask_range
   → 500 silent fail.

Changes (NewProjectDialog.vue):
- Replace default axios import с apiClient + ensureCsrfCookie +
  extractErrorMessage из api/client.ts (same pattern как NewDealDialog).
- await ensureCsrfCookie() перед mutating; apiClient.post/patch.
- Remove regions <v-autocomplete> + selectedRegions ref + inverted
  region_mode watcher (interim A — proper 89-codes реализация в Plan 6).
- Add general error banner для non-422 ошибок (419/401/500/network).
- form.region_mask=255 + region_mode='include' (schema default = вся РФ).

Changes (EditProjectDialog.spec.ts):
- Switch mock с default axios на apiClient (cascading from above).

Verified: Pest 742/739/3sk/0, Vitest 758/3sk/0, vue-tsc 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:28:33 +03:00
Дмитрий cb05657f30 chore(format): prettier --write across 37 .vue/.ts files
Phase 1B audit found 48 files failing `prettier --check`. Auto-apply
via `npx prettier --write resources/js/**/*.{ts,vue,css}` produced
style-only changes:
- consistent quote style
- trailing comma normalization
- spaces around : in v-card style="position: relative" attrs
- explicit ; insertion

No semantic changes. No code-behavior changes. Production-code only;
test files batched separately into `test(frontend):` commit.

Verification:
- npx vitest run → 79/79 files, 614/614 + 3 skipped (no regression).
- npx vue-tsc --noEmit → 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:24:33 +03:00
Дмитрий 55a9d3fe00 fix(types): unify Project interface + NavItem.countKey + drop legacy Record
vue-tsc was emitting 9 errors from two issues:

1. ProjectCard.vue had a local `interface Project` missing region_mask /
   region_mode / delivery_days_mask, while stores/projectsStore.ts
   exported the canonical one with those fields. ProjectsView.vue passed
   the canonical Project to ProjectCard handler signatures which expected
   the local incomplete one → 5× TS2322.

2. EditProjectDialog passed `project: Project | Record<string, unknown>`
   to NewProjectDialog which expected `Record<string, unknown> | null`.
   Project lacks an index signature → TS2322.

3. AppSidebar.vue template referenced `item.countKey` not declared in
   NavItem interface → 2× TS2339.

Changes:
- ProjectCard.vue: drop local Project, import from projectsStore.
- NewProjectDialog.vue: project prop type → Project | null (was Record).
  Drop `as { id: number }` cast on PATCH URL.
- EditProjectDialog.vue: project prop type → Project | null.
- AppSidebar.vue: add `countKey?: string` to NavItem.
- projectsStore.ts: make region_mask/region_mode/delivery_days_mask
  optional (backward-compat for mock fixtures; production rows always
  populate them by schema).
- Test/story fixtures expanded with delivered_today/is_active/archived_at/
  sync_status to match strict Project shape.

Verification:
- npx vue-tsc --noEmit → 0 errors (was 9).
- npx vitest run on 5 affected specs → 16/16 passed + 2 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:15:26 +03:00
Дмитрий 3fc90f12df feat(projects-ui): Quiet Luxury redesign for card-check + 5 dialog v-text-fields
ProjectCard.vue: replace 2px noir solid border on .card-check__box with
1px var(--liderra-line) idle / var(--liderra-line-strong) hover / var(--liderra-teal)
checked. Checked state uses tonal 10% teal bg instead of full fill. Size 20→16px.
Added :focus-visible outline for keyboard nav.

NewProjectDialog.vue: add a local .ld-input-quiet class to all 5 v-text-field
in the dialog (domain / phone / sms keyword / name / daily limit). The class
overrides v-field outline border-color through :deep() to use the tokens.css
1px line / line-strong / teal palette, and sets border-radius to var(--radius-8).
All variant/density/color values come from Vuetify global defaults in
plugins/vuetify.ts:50-54. Includes opacity:1 on every override to neutralize
Vuetify's --v-field-border-opacity 0.38 cascade, plus an explicit error-state
rule with border-color:currentColor to preserve Vuetify's red error border.

Twin elements left out of scope: .toolbar-check__box in ProjectsView.vue,
v-combobox/v-autocomplete/v-btn-toggle inside the same dialog, and the
filter-bar v-select inputs.

Spec: docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md
Plan: docs/superpowers/plans/2026-05-12-quiet-luxury-elements-1440-896.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:07:20 +03:00
Дмитрий 0245f12b51 chore(dev): inject DevIndexBadge for visual feature feedback on localhost 2026-05-12 04:45:18 +03:00
Дмитрий 1c3989a6df feat(frontend): Plan 5 Task 10 — EditProjectDialog wrapper + BulkActionsBar + 7 tests 2026-05-11 19:41:53 +03:00
Дмитрий 92082606e3 feat(frontend): Plan 5 Task 8 — ProjectsView + projectsStore (no polling) + 9 tests 2026-05-11 19:38:59 +03:00
Дмитрий 8bc7838f0c feat(frontend): Plan 5 Task 9 — NewProjectDialog (3 tabs Site/Call/SMS) + story 2026-05-11 19:31:26 +03:00