Commit Graph

481 Commits

Author SHA1 Message Date
Дмитрий 2f6e88e784 feat(sales): замечания владельца по воронке — богатая карточка, сортировка созвонов, счётчики фильтра
Три правки после демо Этапа 1 (все по TDD):
1. Карточка показывает ВСЕ данные поиска из payload (тип ProspectPayload 1:1 с
   dataclass Firm; computed infoRows рендерит юрлицо, директора+личный ИНН,
   контакты, бюджет Директа вилкой, каналы/коллтрекинг, оценку). Демо-сидер
   кладёт полный синтетический payload.
2. Начальнику отдаётся manager_counts; фильтр показывает «Имя (N)».
3. Колонка «Переговоры» сортируется по next_call_at ASC (просроченные сверху).

Гейты: бэк 12/12, фронт 19/19, Larastan 0. НЕ выкачено (ждём разрешения владельца).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:52:54 +03:00
Дмитрий 869590a1b2 feat(sales): маршруты + пункты меню воронки (менеджер/начальник)
Этап 1 Task 14. /sales/prospects (менеджер), /sales/prospects/board
(начальник, salesBossOnly). Пункты «Потенциальные клиенты» и «Воронка отдела».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:46:54 +03:00
Дмитрий 4b5afacfb7 feat(sales): экраны воронки — менеджер (свои) + начальник (все + фильтр)
Этап 1 Task 12–13. SalesProspectsView (менеджер, listProspects свои),
SalesProspectsBoardView (начальник, listSalesManagers фильтр, все карточки).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:44:29 +03:00
Дмитрий 4212380d6c feat(sales): доска-канбан прогнозов (8 колонок, клик→диалог, просрочка)
Этап 1 Task 11. Без drag: карточки раскладываются по стадиям, клик эмитит
open, просроченный next_call_at подсвечивается.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:42:08 +03:00
Дмитрий bb2fe2f34d feat(sales): диалог карточки прогноза — результат разговора + валидация
Этап 1 Task 10. Селектор результата, поля по стадии, отказ скрыт при user,
обязательные причина/время. Тесты через vm (v-dialog телепортит контент).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:40:46 +03:00
Дмитрий ead1335242 feat(sales): фронт — API прогнозов + утилита стадий/просрочки
Этап 1 Task 9. PROSPECT_STAGES (8 колонок), stageMeta, isOverdue;
listProspects/updateProspect в api/sales.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:38:07 +03:00
Дмитрий aab142c381 feat(sales): ссылка «Поиск клиентов» в кабинете начальника → /finder/
В боковом меню кабинета отдела продаж (блок «Начальник», только role=head)
добавлена внешняя ссылка на Python-поиск клиентов (2ГИС), развёрнутый отдельной
службой под lk.liderra.ru/finder/. Открывается в новой вкладке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:02:08 +03:00
Дмитрий 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
Дмитрий e517b7a256 Merge remote-tracking branch 'gitea/main' into worktree-jivo-bot-core
# Conflicts:
#	app/bootstrap/app.php
#	app/config/services.php
#	app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php
#	db/CHANGELOG_schema.md
#	db/schema.sql
2026-07-14 09:44:56 +03:00
Дмитрий c687694f43 fix(portal): убраны машинные слова, которые видит клиент
В «Отчётах» клиенту показывали «retry-failed для owner отчёта», «3 попыток
retry» и «Готовится асинхронно». В «Конкурентном поле» подсказка звала
элементы «подкомпаниями / ЖК» (чужая ниша). Переписано по-человечески:
перезапустить до 3 раз за 7 дней, готовится в фоне, элементы — филиалы или
отдельные бренды.

Тест на баннер отчётов теперь запрещает слово «retry» в интерфейсе.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:36:32 +03:00
Дмитрий 5cb742926b fix(portal): дата в истории слияний слипалась с названием + чужая подсказка в форме
«Объединено в Белый Зуб14.07.2026» — между названием и датой не было разделителя.
Подсказка поля «Название элемента» звала пример из чужой ниши («ЖК Нанжуль-
Солнечный») — заменена на нейтральную.

Демо-стенд: лента «Активность» в карточке заявки была пустой (событий у демо-заявок
не было вовсе), журнал кошелька копил кривую хронологию прошлых прогонов — списания
стояли раньше пополнений. Журнал денег защищён от подделки (только дописывать), поэтому
сброс демо-журнала делается ЯВНО и только на локальной машине.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:17:34 +03:00
Дмитрий 261a3cd46f fix(portal): проект на паузе больше не пишет «Собирает заявки» + итог колонки в «Канбане»
Два бага, которых не видел ни один тест — их нашёл робот, снимавший инструкции
для гостей.

1) Карточка остановленного проекта показывала одновременно «На паузе» и зелёную
плашку «Собирает заявки»: плашка смотрела только на связь с поставщиком (там
всё «ок») и про паузу не знала. Теперь серым: «Остановлен вами — заявки не
приходят».

2) В «Канбане» сумма в шапке колонки всегда была прочерком: считалась по старому
демо-полю, которого у живых заявок нет (там ноль), хотя на карточках стояла
честная цена. Теперь считает по настоящему списанию.

Тесты: сначала падающие, потом фикс. Весь фронт 1229 зелёных.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 08:58:33 +03:00
Дмитрий b6b8d6e0d6 feat(visitors): раздел «Посетители» в админке — воронка, каналы, визиты, кабинет
Воронка подсвечивает красным шаг, где теряем больше всего людей. Гости с VPN
и хостингов показаны отдельным числом, а не подмешаны в конверсию. Город при
VPN честно помечается как недостоверный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 05:03:38 +03:00
Дмитрий 157f277130 feat(visitors): маячок портала (view/alive/register_open/portal_screen) 2026-07-13 20:02:56 +03:00
Дмитрий 6e930400cd feat(portal): подсказка про качество источника при создании проекта из чужого источника 2026-07-13 14:04:49 +03:00
Дмитрий 303bca9081 fix(bot): свой разговор на каждый вход — чужие цифры не остаются в браузере
Ключ разговора в localStorage теперь привязан к тому, кто вошёл:
'liderra_chat_id_u<id>' в кабинете, 'liderra_chat_id_guest' на лендинге.
Смена вошедшего (вход/выход/вход под другим) очищает переписку на экране
и заводит новый ключ — иначе следующий человек за тем же компьютером
видел бы баланс и проекты прошлого клиента (окошко долбилось бы в чужой
разговор и получало 404 от серверной проверки владельца).
2026-07-13 12:11:48 +03:00
Дмитрий e969b7700a chore(chat): убрать настройки Jivo — чат теперь свой; экскурсии выключены
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.

Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:32:21 +03:00
Дмитрий ac0c8f74fc feat(chat): окошко чата на портале и лендинге вместо виджета Jivo 2026-07-13 08:09:51 +03:00
Дмитрий 47afefbb10 feat(bot): цены бот тянет сам из настроек — вручную актуализировать больше не надо
Вопрос владельца: «а цены в нём надо актуализировать руками или он сам тянет?»
Ответ был неприятный: руками. Цифры (300/50 руб, тарифные ступени) стояли текстом
в статьях — поменяли бы цену в админке, бот продолжил бы врать про деньги.

Теперь в статьях стоят метки {{цена_подбора}}, {{цена_изучения}}, {{тарифы}},
а сервис LivePrices подставляет живые значения из system_settings и pricing_tiers
В МОМЕНТ ОТВЕТА — без переиндексации базы знаний. Нет цены в настройках — метка
не протекает клиенту, бот отсылает к экрану, где цена написана всегда.

Из текстов экскурсий цифры цен тоже убраны (фронт не знает БД): шаг подсвечивает
строку с ценой в самом окне — она всегда актуальная.

Проверено живьём: поменял цену в настройках на 450 — бот сразу назвал 450.
Pest 42/42 (3 новых теста), Vitest каталога 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:06:04 +03:00
Дмитрий fddba0a616 feat(bot): шаг «проверьте ссылки перед переносом в поле» + ревизия инструкции на грабли
Владелец: «клиентам надо подсказывать — от того, как они сформируют поле,
зависит весь дальнейший поиск». Плюс просьба вычитать инструкцию и экскурсии
на враньё и ловушки.

Добавлено предупреждение (и в экскурсии collect-competitors/proposals, и в статьи):
перед переносом конкурента в поле открыть сайт и справочник ссылками — тот ли это
бизнес, тот ли город, живой ли адрес. По грязной карточке мы найдём не те источники,
а изучение каждого конкурента стоит 50 руб.

Ревизия (агент сверил 36 статей и 31 экскурсию с кодом) — закрыто:
1. Вебхук обещал автоматическую отправку событий, которой НЕТ. Теперь честно:
   сейчас только тестовый запрос; заявки забирать по API-ключу или выгрузкой.
2. Колонка «Город»: оговорка, что в редком запасном случае там регион проекта.
3. Отчёты: фильтры «Проект»/«Менеджер» пока только «Все» — сказано прямо.
4. Цены автоподбора: «актуальная цена всегда в самом окне».
5. Статьи «Портал на телефоне» и «Откуда заявки» больше не ведут на чужую
   экскурсию про сделки (у них своей нет — кнопки просто не будет).
6. Синонимы: «портал на телефоне» и «заявки в свою CRM» находили не то.

Pest 39/39, Vitest 1227/1227. Живой прогон вопросов — отвечает верно.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:50:19 +03:00
Дмитрий 3e9ce2091e fix(tours): карточка экскурсии не уезжает за экран и не тонет под виджетом чата
Владелец застрял на шаге «Цена» (экскурсия про сбор конкурентов): карточка встала
ниже видимой части окна и вдобавок была накрыта виджетом Jivo — кнопку «Далее»
нажать было нечем, экскурсия превращалась в тупик.

1. Позиция карточки прижимается к экрану (clampTop): она всегда целиком видна,
   какой бы низкой ни была цель шага.
2. z-index экскурсии поднят выше Jivo (виджет рисует себя поверх всего).

Оба бага сначала воспроизведены тестами. Vitest 1227/1227. Живьём: все 4 шага
экскурсии collect-competitors — карточка в экране, «Далее» кликабельна.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:18:18 +03:00
Дмитрий d1f3b0581e fix(tours): ссылка бота на экскурсию переживает вход и не перебивается приветственным туром
Владелец нажал «Показать на портале» в чате и не смог посмотреть: его выкинуло
на «Вход», после входа увело на дашборд (адрес экскурсии потерян), а сверху сел
приветственный тур «Добро пожаловать в Лидерру».

1. LoginView теперь возвращает клиента по redirect (роутер его уже сохранял) —
   /dashboard?tour=dupes открывается сразу после входа. Чужой адрес в redirect
   игнорируется (защита от подмены ссылки).
2. WelcomeTour молчит, если в адресе есть ?tour= — экскурсия из чата важнее.

Оба бага сначала воспроизведены тестами. Vitest 1225/1225. Проверено живьём:
гость → ссылка бота → форма входа → сразу «Шаг 1 из 4» нужной экскурсии.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:10:00 +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
Дмитрий e5f1ff9a4f fix(tours): карточка подсказки не уезжает за экран — справа/слева/снизу от цели
Найдено живым показом: у кнопки «Создать проект» (правый край) карточка
позиционировалась за пределами окна. Vitest GuidedTour 5/5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:29:05 +03:00
Дмитрий 93f2307aec feat(bot): дообучение под нововведения — Конкурентное поле, счета, подарок 1000₽, новые Сделки, мобильный
Статьи: +konkurentnoe-pole (сбор 300₽/изучение/пакет), пополнение (счета+акт+подарок),
проект (второй путь через Поле, колонки Конкурент/Источник, мобильные вкладки).
Экскурсия konkurentnoe-pole (nav-autopodbor). Pest Bot|Tour|Help 49/49.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:01:29 +03:00
Дмитрий cd62806da9 feat(tours): data-tour якоря — кнопки создания проекта и пополнения 2026-07-10 08:49:41 +03:00
Дмитрий bf8174dbfc feat(tours): запуск экскурсии по ?tour= из ссылки бота 2026-07-10 08:49:40 +03:00
Дмитрий 9b5ddcb6cd feat(tours): GuidedTour — обобщённый раннер с ожиданием цели 2026-07-10 08:48:53 +03:00
Дмитрий 8526fa3389 feat(tours): каталог экскурсий — 5 стартовых сценариев 2026-07-10 08:48:53 +03:00
Дмитрий 97c75407ab fix(autopodbor): RLS-политика межтенантного распорядителя + фронт 422-дискриминатор + price=0 гейт
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:45:12 +03:00
Дмитрий d81cdaffac feat(autopodbor): кнопка «Собрать источники (N)» в подвале + яркий алерт баланса 2026-07-09 09:45:12 +03:00
Дмитрий dc64226ec3 fix(portal): карточки вместо таблицы на планшете-портрете (Сделки/Списания)
Широкая таблица (7 колонок) не влезала в узкую колонку планшета рядом с
rail-меню — правые колонки «Комментарий»/«Поставлен» обрезались на 600–1279px.
ResponsiveTable теперь показывает карточки на телефоне И планшете, таблицу —
только на десктопе (>=1280). Тесты табличного слоя зафиксированы на десктоп-ширину.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:57:07 +03:00
Дмитрий 987cd0ac58 fix(deals): колонка «Конкурент» показывает только бренд до первой запятой
Имя конкурента хранится как «Бренд, описание» — в колонке «Конкурент»
показываем часть до первой запятой, полное имя — в title при наведении.
Применено к десктопной таблице и мобильной карточке. +тест на обрезку.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:37:30 +03:00
Дмитрий 2bbe2a7612 feat(metrika): Метрика+Вебвизор в линию портала (чтобы выкаты не теряли)
Метрика была потеряна, т.к. клалась мимо конвейера: выкат = git archive HEAD ветки
+ пересборка, а Метрика жила отдельно → перезатёрлась. Кладём в feat/portal-mobile-adaptive
(линию, из которой режутся deploy/*-ветки) → любой будущий выкат несёт её сам.

Загрузчик metrika.ts (только кабинет layout=app), маскировка ПДн лидов
(ym-hide-content/ym-disable-keys) — десктоп-таблица + МОБИЛЬНЫЕ карточки (телефон+
комментарий+aria-label) + канбан + карточка сделки + диалог. Сборка проходит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 17:00:37 +03:00
Дмитрий 77050dbefb fix(admin): клиентский диапазон ступеней в тарифной сетке + предпросмотр в редакторе
Cherry-pick a7a4e735 поверх боевой ветки feat/portal-mobile-adaptive для
выката ТОЛЬКО правки тарифов на прод. Логика денег не менялась.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:29:40 +03:00
Дмитрий a7a4e7356a fix(admin): клиентский диапазон ступеней в тарифной сетке + живой предпросмотр в редакторе
Админка показывала сырую ширину ступени (leads_in_tier: 250/500/1000...),
а кабинет клиента — накопительный диапазон (1-250, 251-750...). Одни и те же
данные, но выглядят как разные тарифы; в форме редактирования подпись
'Лидов в ступени' читается как потолок, а система понимает её как ширину -
ловушка при смене цен.

Логика денег не менялась. Изменён только показ в админке:
- колонка 'Диапазон у клиента' в активной сетке;
- живой предпросмотр диапазона в форме редактирования;
- расчёт диапазона вынесен в общий utils/pricingTiers.ts, чтобы админка
  и кабинет больше не расходились.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:10:08 +03:00
Дмитрий de46ae3314 fix(autopodbor): защита route.query в deep-link — чистый прогон тестов
Влитый из автоподбора deep-link «Сделки → Автоподбор» читал route.query
в onMounted без защиты. В приложении роутер всегда есть, но при монтировании
в юнит-тесте без роутера route === undefined → unhandled error (2 ошибки,
vitest exit 1). Добавил route?.query ?? {} — поведение в бою не меняется,
полный прогон снова зелёный (1184 , exit 0). Правка нужна для чистого
CI-гейта выката мобильной ветки.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 08:44:48 +03:00
Дмитрий 7799f13c79 feat(portal-mobile): новые колонки «Конкурент» и «Источник» в карточке сделки на телефоне
После вливания переделки сделок (колонки Конкурент/Источник со ссылками
в автоподбор) эти поля были только в таблице на компьютере — в карточке
телефона их не было. Добавил в мобильную карточку:
- «Конкурент» — имя конкурента зелёной ссылкой, ведёт в автоподбор
  (/autopodbor?competitor=ID), рядом город;
- «Источник» — отдельной строкой домен/идентификатор ссылкой в автоподбор
  (+ «· +N» если источников несколько) и канал (Сайт/Звонки/СМС).
Для сделок без источника автоподбора карточка выглядит как раньше
(проект · канал · город). @click.stop — ссылки не открывают деталь сделки.

Проверено на 390px: конкурент+источник видны, вбок не едет, переход
по ссылке в автоподбор работает; тесты DealsTable/mapper/ResponsiveTable
зелёные (23).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 08:26:29 +03:00
Дмитрий e47bd5b894 Merge branch 'worktree-avtopodbor' into feat/portal-mobile-adaptive 2026-07-08 08:16:51 +03:00
Дмитрий 98614604af feat(сделки): колонки «Конкурент» и «Источник» со ссылками в автоподбор
Раздел «Сделки»: колонка «Источник» разбита на две — «Конкурент» (ссылка на
экран конкурента) и «Источник» (ссылка на «Настройки проекта»). В модалку
настроек добавлена кнопка пуск/стоп (срабатывает не закрывая окно).

- бэк DealController::index: батч-резолв конкурента+источника по
  deal.project_id → autopodbor_sources.created_project_id → competitor (без N+1)
- контракт ApiDeal/MockDeal + маппер: competitor_*/source_* поля
- DealsTable: 1 колонка → 2 (Конкурент/Источник), router-link, fallback без автоподбора
- AutopodborView: deep-link ?competitor=&project= открывает экран конкурента + модалку
- FieldCompetitorScreen: кнопка ⏸/▶ в модалке настроек + авто-открытие по ссылке

Тесты: бэк DealIndexTest +4 (31 ), фронт +новые (66 ). Дизайн не менялся.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 07:56:30 +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
Дмитрий 1db2fe6581 docs(legal): Яндекс.Метрика/Вебвизор в политике конфиденциальности (ПДн маскируются)
Раздел «5. Хранение и защита»: упоминание записи сессий Вебвизором с маскировкой
персональных данных контактов, обработка на серверах в РФ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:46:57 +03:00
Дмитрий 2d56ad0d48 feat(metrika): ym-disable-keys — поля ввода телефона/имени в кабинете (диалог+фильтр)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:45:39 +03:00
Дмитрий 6b5fa09330 feat(metrika): маскировка ym-hide-content — имя+телефон в карточке сделки и канбане
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:45:21 +03:00
Дмитрий 899e166e88 feat(metrika): маскировка ym-hide-content — телефон+комментарий в таблице сделок; телефон убран из aria-label (id вместо ПДн)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:45:00 +03:00
Дмитрий 0e502c4072 feat(metrika): hit только для маршрутов кабинета (layout=app)
router.afterEach: при входе на layout=app лениво грузит Метрику и шлёт hit(fullPath).
Вход/публичные/админка/портал продаж — без Метрики. + убраны лишние @ts-expect-error
в тесте (type-check чист).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:38:45 +03:00
Дмитрий 05b2dff979 feat(metrika): ленивый загрузчик + hit с идемпотентностью (TDD, 3 теста)
metrika.ts: читает id из <meta name=metrika-id>, один раз инжектит tag.js + init
webvisor:true (defer), hit(url) шлёт заход. Тесты tests/Frontend/metrika.spec.ts:
no-op без meta, идемпотентная загрузка, hit вызывает ym с id+url.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 04:35:44 +03:00