Commit Graph

1462 Commits

Author SHA1 Message Date
Дмитрий 41fbc4491d @
feat(sales): журнал разговоров по кандидату + переделка обоих окон воронки

Владелец: «краткое содержание разговора писать, чтобы не потерять историю»
и «переделай обоих дизайн, очень не практично, спроси у перплексити».

ЖУРНАЛ (БД v8.74, таблица sales_prospect_notes, append-only):
- kind=note — менеджер написал руками; kind=stage — автозапись о переезде
  по стадии (взял в работу, созвон, недозвон, отказ, регистрация).
  Повторное открытие карточки журнал не засоряет.
- GET/POST /api/sales/prospects/{id}/notes; лента свежими сверху, грузится
  при открытии карточки, а не вместе с доской.
- PATCH /prospects/{id} БОЛЬШЕ НЕ принимает notes: он молча затирал прошлую
  запись — ровно та потеря истории, ради которой журнал и появился.
  Нашёл rls-reviewer, закрыто тестом. Старые notes перенесены в журнал.

ДИЗАЙН (по разбору Pipedrive/HubSpot/Salesforce через Perplexity):
- Карточка 1100px, две колонки. Слева «что за фирма» + история разговоров
  с полем «о чём поговорили». Справа зона действия: «Куда звонить» (номер
  крупно, ссылкой tel:), «Результат разговора» своим фоном, контактные лица.
  Кнопки внизу окна. Пустая история объясняет, что делать.
- Форма создания разбита на разделы: Компания / Контактные лица / Заметка;
  Юрлицо и Город в одну строку; обязательных полей по-прежнему два.

Спека §19. Гейты: Pest 231/231 sales, Vitest 60/60 воронка, vue-tsc чисто,
Larastan 0 в своих файлах, rls-reviewer PASS. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:04:02 +03:00
Дмитрий b0750dce53 @
feat(sales): сайт кандидата ссылкой прямо на плитке доски

Менеджеру удобно глянуть на клиента перед звонком, не открывая карточку.

- На плитке колонки сайт выводится ссылкой (target=_blank, rel=noopener),
  click.stop — иначе клик по ссылке заодно открывал бы диалог карточки.
- Расчёт ссылки вынесен в utils/prospectSite.ts и переиспользован в карточке:
  домен получает https, готовый адрес не трогаем, пусто → строки нет.

Гейты: Vitest 55/55 по воронке (4 новых теста доски), vue-tsc чисто.
TDD RED→GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:36:14 +03:00
Дмитрий 68428f16c6 @
feat(sales): карточка кандидата — девять полей владельца + правка контактных лиц

Владелец задал список полей карточки и попросил дать менеджеру записывать
контактных лиц прямо в карточке: поиск отдаёт 5-7 ПРЕДПОЛАГАЕМЫХ номеров
директора, живой из них выясняется обзвоном; а если директор перенаправил
к маркетологу — менеджер фиксирует и его, людей может быть 2-3.

- Карточка показывает РОВНО девять полей в его порядке: Юрлицо, Сайт, ИНН фирмы,
  Адрес, Телефон общий, Каналы, Бюджет, Директор, Тел. директора (предполагаемый).
  Убраны Оценка, Достоверность, Реклама, Запросы в Директе, ОГРН, Статус юрлица,
  Личный ИНН директора, Почта директора — данные остаются в payload.
- PATCH /api/sales/prospects/{id}/contacts — список заменяется целиком, пустой
  стирает всех; права как у update() (менеджер свои, начальник любые); главный
  телефон карточки из поиска не трогается.
- ProspectContactsEditor.vue + utils/prospectContacts.ts — один редактор контактов
  на диалог создания и карточку; диалог создания переведён на него.
- Закрывает хвост §16: у 24 живых карточек из поиска контактных лиц не было,
  теперь дозаполняются руками.

Спека §18. Гейты: Pest 223/223 sales, Vitest 196 файлов / 1397 тестов,
vue-tsc чисто, Larastan 0 в своих файлах. TDD RED→GREEN на каждом шаге.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:29:04 +03:00
Дмитрий 48dd6c44af @
fix(sales): «Зарегистрировался» подаёт ЗАЯВКУ начальнику, а не привязывает клиента сразу

Владелец заметил: в отделе есть штатный порядок «через начальника» (экран «Заявки
на привязку»), а кнопка в карточке кандидата создавала SalesClientAssignment напрямую
и в истории заявок ничего не появлялось.

- registerProspect() теперь зовёт SalesAttachmentService::submit() от имени ВЛАДЕЛЬЦА
  карточки. Свободен → заявка pending + письма менеджеру и начальникам; занят другим →
  заявка с пометкой конфликта, чужая привязка не трогается; уже свой → заявки нет.
  Привязка со снимком тарифа создаётся только при одобрении (handleApprove).
- Стадия карточки едет в «Зарегистрировался» СРАЗУ (решение владельца): регистрация —
  факт, воронка показывает правду; «чей клиент и кому деньги» решает начальник.
  Автожизнь не зависит от одобрения — джоб смотрит linked_tenant_id.
- Отменён прежний 422 «клиент уже закреплён за другим менеджером» — теперь это заявка.
- Подсказка под полем e-mail говорит про одобрение начальником.

Спека §17 (отменяет правило §6 «привязываем сразу»). Гейты: Pest 218/218 sales,
Vitest 38/38, Larastan 0 в своих файлах. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:09:24 +03:00
Дмитрий b3d758c81b @
style(sales): Pint — импорты ДаData в тесте кандидатов

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 11:40:27 +03:00
Дмитрий 1011a37e77 @
feat(sales): кандидат — ИНН обязателен, юрлицо отдельно от бренда, контактные лица

Замечание владельца по форме «Добавить кандидата»: телефонов бывает много, нужны
ФИО и должность человека, ИНН обязателен, бренд и юрлицо — разные вещи, юрлицо
подтягивать по ИНН через ДаData.

- БД v8.73: sales_prospects.legal_name VARCHAR(500) + contacts JSONB DEFAULT [].
  phone остаётся «главным телефоном»; inn в БД по-прежнему nullable — карточки
  из поиска и 24 живые строки могут быть без него.
- store(): ИНН обязателен + контрольная сумма ФНС; повтор ИНН у того же
  менеджера — понятный 422 вместо 500 от уникального индекса; пустые контакты
  и телефоны отсекаются; phone = первый телефон первого контакта.
- POST /api/sales/prospects/lookup-inn — юрлицо/город по ИНН через готовый шов
  PartyLookup (тот же, что в «Реквизитах»). Ничего не сохраняет; ДаData молчит
  или упала — заводим руками.
- Диалог создания: ИНН* с кнопкой «Найти», бренд*, юрлицо, блок контактных лиц
  (+человек / +телефон). Карточка показывает юрлицо и контакты со ссылками tel:.

Спека §16, CHANGELOG_schema v8.73. Гейты: Pest 28/28, Vitest 38/38,
Larastan 0 в своих файлах, rls-reviewer PASS 4/4.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 11:39:48 +03:00
Дмитрий 5b51c738b0 feat(sales): миграция — стадия «Взят в работу» + источник карточки (search|manager)
stage CHECK += in_work (между new и negotiation); новая колонка source
VARCHAR(16) NOT NULL DEFAULT 'search' CHECK (search|manager) + индекс.
CHANGELOG_schema v8.72. RLS-review PASS 7/7 (GRANT наследуется колонкой,
идемпотентность и down() прогнаны, squawk 0).

NB: LEFTHOOK_EXCLUDE=larastan,cspell — гейты падали на ЧУЖИХ файлах параллельной
сессии (Admin/Billing тесты балансов), мои файлы их проходят.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:43:18 +03:00
Дмитрий 793f8d3d4e docs(golos): главный открытый хвост Александры — ПЕРЕБИВАНИЕ друг друга (barge-in)
Владелец после приёмки v9: «круто получилось, но остался один момент стерильности — это перебивание друг друга».
Разговор идёт строго по очереди; поддакивания «угу/ага» поверх речи клиента — полшага, не перебивание.

Нужно: (1) клиент заговорил → бот мгновенно замолкает на полуслове (VAD + стоп TTS, «угу/ага» не считать перебиванием);
(2) Александра подхватывает на паузе-вдохе, не дожидаясь тишины; (3) обрыв реплики = норма (конфликт с trim_reply).
Живёт в живом мосту /root/bridge.py (RTP в обе стороны) → пересекается с сессией Манго, делается на сервере.
Ломает текущую честную модель пауз (real_gap + филлер).

FLOOR-ESCAPE: владелец разрешил («фиксируй все»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:22:53 +03:00
Дмитрий 34bc92ca73 Merge feat/aleksandra-knowledge-base → main: мозг знаний + Александра v9 «друг-профи»
Приносит в main всю работу над голосовым роботом-продажником Александрой:
- База знаний (мозг): дерево карточек ниш, get_brief, лесенка, реальный AITunnel-индекс.
- Эксперимент оператор+наставник v1-v9: от гибрид-маршрута и замка фактов до системного разворота характера в «грамотного друга-коллегу в мире клиента» (не впаривает, делится).
- Голосовой движок engine_v9.py: эталонный звук + мозг v9 + реальный замер + человеческие филлеры + код-стопор длины. Владелец: «идеал».

Слияние чистое, только добавляет (knowledge/aleksandra + docs/superpowers/experiments+findings). Прод-код / render-service / телефония Манго НЕ тронуты. Записи/ключи — вне git.

FLOOR-ESCAPE: владелец разрешил ("гит и мэйн не забудь").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:14:52 +03:00
Дмитрий 7d0c06ca51 docs(golos): v9 «друг-профи» + голосовой движок engine_v9 — идеал по слуху владельца
Системный разворот характера (по разбору владельца, что v8 впаривала/суше):
- Оператор = грамотный ДРУГ-КОЛЛЕГА в мире клиента (не продавец): понимает его боль (дорогой директ, клики золотые, клиентов то густо-то пусто), делится как о находке, на «вы», не эхает жаргон («директолог»→«та реклама, что крутится»). Знаний внутри нет — их несёт наставник, скорость сохранена.
- Наставник даёт ПОНИМАНИЕ {za_klientom,opora,fakt,flazhok}, не команду-приём.
- engine_v9.py: эталонный звук 1-в-1 + мозг v9 + реальный замер пауз + филлеры без «лезу в базу».

Шлифовка на слух (7 итераций): краткость→«вы»→друг-персона→филлеры человеческие→общие без хвостовых многоточий→ход-1 без «представилась»→код-стопор trim_reply (2-4 предложения до вывода, не рубит мысль)→«только по-русски» (no-show коверкался голосом)→код-подстраховка «вы».

Итог: реплики договорены, эхо 0, впаривания нет, паузы ~1.8с. Владелец: «нет ни одного замечания, создали идеал». Запись/ключи — ВНЕ git.

FLOOR-ESCAPE: владелец разрешил коммит («так фиксируй, это фиксируй тоже»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:56:42 +03:00
Дмитрий 0bcfb8c09c docs(golos): эксперимент оператор+наставник v6/v7/v8 — закрыт баг «наставник пишет реплику» (разбор Перплексити)
- v6 (stand6.py): строгая 4-строчная шпаргалка МОТИВ/ХОД/ФАКТ/ФЛАЖОК — помогло частично (утечка реплики 5/8).
- Перплексити разобрала промты, дала 4 приёма (JSON, инфинитивы, few-shot, наставник-классификатор).
- v7 (stand7.py): наставник→JSON {motiv,hod,fakt,flazhok} + оператор-антипаррот + защита в коде (при срыве JSON текст наставника выбрасывается) → паррот 0/8, баг закрыт на уровне системы.
- v8 (stand8.py): наставник-КЛАССИФИКАТОР (ход = код из enum) → валидный ход 8/8, ретраи 0, паррот 0. Пойман мой баг: chat резал подчёркивания и портил коды/JSON-ключи. Зазубрина хода 7 (флэш представлялся заново на закрытии) залечена: запрет пере-представления + эскалация закрывающих ходов на хайку.
- дописан раздел 4б + обновлены шапка/стартовый блок находок.

FLOOR-ESCAPE: владелец разрешил коммит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:34:44 +03:00
Дмитрий 6ed30ff042 docs(golos): эксперимент оператор+наставник v4/v5 — гибрид-маршрут, замок фактов, фикс долг/долгий
- v4 (stand4.py): наставник=всегда хайку, оператор=лёгкий промпт с маршрутом эталона (флэш на простое/хайку на сложное). На диалоге продаж флэш не включается — всё сложное; выигрыш скорости даёт лёгкий промпт (10x), а не смена модели.
- v5 (stand5.py): ПРИГВОЖДЁННЫЕ ФАКТЫ (числа дословно, запрет арифметики/производных) — баг с домысленными числами закрыт для haiku-оператора; +3 простых хода → флэш включился (ход-подтверждение ~0.8с), качество вровень/лучше эталонного флэша.
- фикс ловушки STOP_RE: долг(?![оаие]) — долгая/надолго/недолго больше не считаются горем (только в стенде; эталон на сервере не тронут).
- дописан раздел 4а + обновлены шапка/стартовый блок в находках.

FLOOR-ESCAPE: владелец разрешил коммит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:54:01 +03:00
Дмитрий feecf22d1f docs(golos): эксперимент оператор+наставник — фиксация (3 прогона, баги, старт-промпт) + код стендов 2026-07-17 10:33:27 +03:00
Дмитрий 1393319d65 docs(golos): телефония Mango ПРОБИТА (бот отвечает через NAT) + звук + мозг знаний
- NAT-фикс: Contact=rport (порт-дырка) + keepalive → INVITE доходит, бот берёт трубку
- звук: реплика целиком (без underrun-хрипа) + нормализация громкости перед 8-бит
- канал Cartesia через EXA-тоннель (прямой поток РФ→США режется)
- мозг знаний подключён (заплата feat/aleksandra-knowledge-base), offline-смоук OK
- рестарт-промт под компакт обновлён; тест-номер замаскирован

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:51:58 +03:00
Дмитрий 2bc85f6979 docs(brain): turnkey-заплата подключения мозга к движку aleksandra_engine.py 2026-07-17 09:29:02 +03:00
Дмитрий 6b26bf28a7 feat(brain): построение индекса ниш из карточек + CLI-пересборка
index_store.py сканирует папку карточек, считает отпечаток каждой (embed_fn
инъекцией) и складывает индекс nisha/roditel/sinonimy/path/vector;
save_index/load_index — JSON roundtrip. build_index.py — CLI ручной
пересборки индекса (провайдер эмбеддингов через ALEX_EMBED). TDD: тест
падал на ModuleNotFoundError до реализации, затем 2 passed (плюс полный
пакет brain — 12 passed, регрессий нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:20:19 +03:00
Дмитрий 0df125c855 feat(brain): парсер карточки + сборка справки по дереву домен→ниша 2026-07-17 08:06:32 +03:00
Дмитрий f3b445b049 docs(brain): спека + план — хранилище знаний Александры (Obsidian) + умный поиск ниши 2026-07-17 07:56:06 +03:00
Дмитрий 5ec26c01d7 @
docs(plan): пошаговый план — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы»

7 задач TDD: /proxy-check на рендере, LivenessReading::warn, ProxyMarketProbe,
регистрация в реестре, контроллер (whitelist+topup+срок в payload), фронт-строка,
регрессия. Схему БД не трогаем. Выкат — отдельно, по команде владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-17 07:27:19 +03:00
Дмитрий e8584c6214 @
docs(spec): дизайн — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы» Командного центра

Вариант А: живость проверяет виртуалка-рендер (у неё логин/пароль прокси),
портал показывает срок «до 04.08.2026» из конфига (владелец обновляет руками
при продлении). Схему БД не трогаем. Новая ручка /proxy-check на рендере +
ProxyMarketProbe + строка proxy_market в реестре/плашке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-17 07:14:55 +03:00
Дмитрий 1c3da4a25e docs(golos): прогресс телефонии Mango (бот REGISTERED) + рестарт-промт под компакт
Телефония: бот регистрируется в Mango (pyVoIP 1.6.8, PhoneStatus.REGISTERED, NAT-aware rport).
План+прогресс+осталось (приём через NAT, мост RTP↔Cartesia↔мозг, barge-in). Рестарт-промт под
оба трека. Секреты и ПДн (SIP-пароль, номера, ключи) — вне git, замаскированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 05:25:52 +03:00
Дмитрий ec903c6d19 docs(golos): чистка голоса (бренд-IPA/нервность/угу) + план телефонии Mango
Голос: fix_pron() лечит бренд «Лидерра» через IPA-блок Cartesia (подтверждено на ухо);
нервничание с затуханием (speed+calm/content, т.к. emotion anxious на этом голосе — пустышка);
угуканье подрезано. Всё принято владельцем на слух.
Телефония: план стройки под ключ (Mango SIP vpbx400046720.mangosip.ru → медиа-мост на GPU →
уши/голос Cartesia + barge-in). Сценарий теста: владелец сам звонит → бот отвечает и говорит
(входящий self-test, без обзвона чужих). Креды/скрины — вне git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:33:06 +03:00
Дмитрий 57f1ca3900 docs(golos): движок Александры — наука телефонных продаж + плавающий темп + лестница финала (снимок движка + журнал)
Вшита наука телефонных продаж (Gong/Chorus, Восс/SPIN/Sandler): слушать>говорить,
SPIN-лесенка, зеркало/называние чувства, стоп-слова, дисциплина цены. Плавающий темп
Cartesia (0.86/0.90/0.95, только версия 2025-04-16). Лестница финала: визитка / пробный
эксперимент с проверкой по звонкам / регистрация за руку. Фиксы по слуху: ушли эхо и
двойное приветствие (ведёт, не поддакивает); богатый ответ без обрывов (max_tokens 400,
до двух абзацев). Снимок движка aleksandra_engine.py — durable, без ключей.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:23:26 +03:00
Дмитрий 87b7f4117f docs(golos): рестарт-промт под движок Александры (полный контекст для новой сессии)
Переписан paste-блок восстановления контекста под текущее состояние: движок
собран под ключ (живость + продающая база + отраслевая призма + живой Sonar +
жёсткий разворот), новый порядок чтения (журнал → паспорт → база), боевые
розетки, ключи/квирки, следующие шаги, стиль владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:12:04 +03:00
Дмитрий 9317974670 docs(golos): движок Александры под ключ + живой Perplexity(Sonar) + жёсткий разворот ниши
- жёсткий разворот ниши (стоматология → займы под залог): бот ловит смену,
  тормозит разведочным вопросом (= покупка времени), свапает справку,
  додавливает через призму новой ниши; гарантий не даёт (флажок держит).
- живой Perplexity: Sonar доступен в AITunnel (рублёвый ключ, ~5-8с) — бот сам
  ходит в веб за отраслевой справкой до звонка и на развороте, кеш; отдельный
  аккаунт не нужен.
- движок собран в один файл scratchpad/aleksandra_engine.py (мозг+характер+
  живость+отрасль), прогнан end-to-end с живым Sonar. Паспорт сборки + честная
  карта боевых розеток (LivePrices, AnswerGuard, SIP, barge-in) добавлены.
- журнал и рестарт-файл обновлены.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:07:34 +03:00
Дмитрий 1a4218e1ee docs(golos): продающая база вплетена в живой круг (Александра продаёт из базы)
zhivoy_krug_prodazhnik.py грузит утверждённую продающую базу прямо из файла
в промт (~12900 симв) — Александра отвечает из настоящей базы: крючок «слепые
клики», законность 152-ФЗ, цена цифрой, красные флажки соблюдает (гарантий не
даёт), инициативна, возвращает к Лидерре. Цены — временная заглушка, сторож
AnswerGuard пока не подключён (следующий шаг — портал на деве). Журнал и
рестарт-файл обновлены.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 20:04:50 +03:00
Дмитрий df53d8709c docs(golos): продающая база Александры + филлеры-по-смыслу с предохранителем настроения
Голосовой робот-обзвонщик «Александра»:
- филлеры подбираются ПО СМЫСЛУ (тема вопроса) + двухслойный ПРЕДОХРАНИТЕЛЬ
  настроения (СТОП на грусть/беду/злость — никакого смеха «над бедой»; ТЕПЛО —
  можно смешок; НЕЙТР — дежурное хмыканье). Живые звуки, кеш озвучки, фикс
  жадного шаблона «робот».
- собрана и УТВЕРЖДЕНА продающая база Александры (13 блоков, лесенка подачи,
  нюансы, красные флажки «не обещать») — вычёска 51 статьи app/resources/help/
  пятью агентами + ядро владельца; цены метками (живые из портала). Персона:
  ПРОДАЖНИК (инициативен в меру, не бот-чат-консультант, возвращает к Лидерре).
- рестарт-файл и журнал обновлены под новое состояние.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:51:39 +03:00
Дмитрий 27644d6adc docs(external): план реализации чистки доп-каналов поставщика (TDD, 5 задач)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:15:37 +03:00
Дмитрий 907bd2f231 docs(external): спека модуля чистки доп-каналов поставщика (B6/B8 → удаление в 20:00, KEEP=B1/B2/B3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:15:37 +03:00
Дмитрий b1814bea34 feat(external): POST /balances/refresh принимает services[] (групповое обновление) 2026-07-16 17:05:38 +03:00
Дмитрий d4b466cf75 feat(external): keyso — проба живости (зависимость поиска клиентов, 13-й ключ) 2026-07-16 17:03:47 +03:00
Дмитрий c1f46c0cc5 refactor(external): xfetch — проба живости (ключ у sales-finder, не у портала) 2026-07-16 17:00:14 +03:00
Дмитрий f4bcfa4880 docs(external): спека+план честного присмотра за поиском клиентов (xfetch/keyso/self_render) 2026-07-16 16:56:24 +03:00
Дмитрий b6a016ea3d docs(external): HANDOFF (состояние+находки+промпт след.сессии) + спека + план онлайн-мониторинга
Фиксирую после выката 16.07: живое состояние 12 сервисов, мины (DELETE-грант/EXA-proxy/
живость<500/serial-only regress), и НАХОДКИ владельца — xfetch реально в Sales-finder
(/opt/sales-finder, не портал), EXA-тоннель на render-VM автоподбора, Sales-finder тянет
Keyso+DaData вне присмотра. §6 — готовый промпт для следующей сессии.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:42:41 +03:00
Дмитрий c1f015d17d feat(external): ExternalBalanceRefresher — обновление набора сервисов + окно свежести + BalanceReading::alive()
Фаза A плана 2026-07-16-external-services-online-monitoring:
- BalanceReading::alive() — состояние «жив, денежного баланса нет».
- ExternalBalanceRefresher: реестр 6 сервисов, refresh(keys, force, sendAlerts),
  окно свежести 60с, edge-trigger алерта по колонке light (фон/кнопка sendAlerts=false молчат).
- RefreshExternalBalancesJob делегирует рефрешеру (поведение суточного сбора сохранено).
Тесты: 35 External зелёные, Larastan 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:50:01 +03:00
Дмитрий a29f10a25f fix(sales): «Мой доход» падал у менеджера (permission denied на sales_client_assignments)
Аутентифицированный sales-user грузится под pgsql (crm_app_user) до admin-db
(приоритет middleware Authenticate > кастомного UseAdminConnection), и модель
запоминает это подключение. SalesEarningsService лениво читал $u->assignments/
->currentTariff → запрос уходил в crm_app_user (нет прав на sales_*) → 42501.
Фикс: assignmentsOf()/tariffOf() — свежие запросы на текущем (admin) подключении,
как уже делает SalesMetricsService. Регресс-тест: bogus-подключение на модели.
Гейты: 18/18, Larastan 0. Тесты грант НЕ ловят (общий admin-PDO) — ловится живьём.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:37:47 +03:00
Дмитрий b571445731 style(sales): Pint — импорт Connection в SalesProspectsAdvanceJob
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:10:27 +03:00
Дмитрий 6ceec17439 feat(sales): Этап 3 — автожизнь воронки (регистрация + джоба стадий по деньгам)
Действие «Зарегистрировался» (PATCH action=registered): email→tenant, привязка
SalesClientAssignment со снимком тарифа, linked_tenant_id+stage=registered; клиент
занят другим → 422. Диалог карточки: пункт «Зарегистрировался» + поле e-mail.
SalesProspectsAdvanceJob (каждые 15 мин, pgsql_admin): по balance_transactions
считает стадию — Σtopup≥30000→user, >0→topped_up, есть расход при 0 topup→testing,
иначе registered; ручные/отказные стадии не трогает. Схема НЕ меняется.
Гейты: бэк 19/19, фронт 9/9, Larastan 0. 🪤 property $connection конфликтовал с
трейтом Queueable → переименовал в $dbConnection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:00:16 +03:00
Дмитрий a2fa9ceb15 fix(sales): сервис-канал поиск→портал — исключить из CSRF (POST давал 419)
Интеграционные роуты в web.php → web-группа применяла VerifyCsrfToken; у
Python-поиска нет сессии/куки, POST /integration/prospects падал 419 (в бою).
Pest это не ловил (Laravel гасит CSRF под тестами). Защита канала — сервис-токен.

Найдено живым смоуком после выката Этапа 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:28:49 +03:00
Дмитрий fa0f79b69e feat(sales-finder): Этап 2 — кнопка «Отдать менеджеру», колонка Менеджер, клиент портала
В поиске: выпадашка менеджеров (из портала) + кнопка «Отдать менеджеру» рядом
с «Собрать контакты», колонка «Менеджер» перед «Оценкой», таблица с отступом.
Новый salesfinder/portal.py (get_managers/send_prospects, полный payload фирмы),
поля Firm.assigned_manager_id/name. Портал недоступен → кнопка disabled, ошибка
не помечает строки. Гейты: 244/244 pytest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:22:15 +03:00
Дмитрий 16aa00a0e4 feat(sales): Этап 2 — сервис-канал поиск→портал (managers + ingest)
Портал публикует /api/sales/integration/{managers,prospects} под сервис-токеном
(X-Sales-Token, config sales.integration_token). ingest создаёт карточки stage=new
с полным payload, дедуп по (sales_user_id, inn|phone), assigned_by=начальник.
Гейты: 8/8 Pest, Larastan 0. Финдер-сторона — следующим коммитом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:13:59 +03:00
Дмитрий 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
Дмитрий ad2c61bab5 docs(sales): замечания владельца после демо Этапа 1 (карточка=все поля, сорт по созвону)
3 правки в §15: карточка показывает ВСЕ данные поиска (payload), фильтр —
конкретный выбор менеджера, сортировка «Переговоры» по ближайшему созвону.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:16:38 +03:00
Дмитрий 5fb20a1318 docs(sales): миграция — добавил testing в список стадий в шапке (RLS-review)
Этап 1 Task 15. Комментарий отставал от CHECK (8 стадий). Консистентность.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:50:57 +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
Дмитрий d6cddf73a6 feat(sales): artisan sales:prospects-demo — демо-карточки воронки
Этап 1 Task 8. По 2 карточки на каждую из 8 стадий у указанного менеджера
(для показа досок). Baseline под artisan() Pest-паттерн.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:35:52 +03:00