Commit Graph

8 Commits

Author SHA1 Message Date
Дмитрий c7bcd7943a fix(прогрев): ночная заливка в Яндекс молчит при нуле номеров, не пишет красную ошибку
При нуле активных номеров (например, после чистки данных) джоб слал в Яндекс
пустую замену состава, Яндекс отвечал 400 «Нет ни одного корректного элемента»,
и на экране начальника повисала красная строчка. Теперь при пустом составе джоб
тихо выходит, погасив прошлую ошибку; сам сегмент в Яндексе сохраняет прежний
состав (пустым его всё равно не сделать — минимум 100). Симметрично VK-джобу.

Три существующих теста конструировали сценарий с нулём яндексовых номеров и
полагались на старую отправку пустой заливки — приведены к настоящему составу
(рядом живая warming-фирма), проверка исключения стала строже.

Larastan исключён: 3 ошибки выше baseline — в чужих tests/Unit/Sms/*SmsProviderTest.php
(параллельная СМС-сессия, не в этом коммите); свои 3 файла stan-чисты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 19:20:15 +03:00
Дмитрий ce48d911d6 feat(прогрев ф2 этап B): заезд = только загрузка во все 4 канала (loaded), без автостарта
Фаза 2 Этап B, Task B1 (+ план этапа B). AdAudienceIntake::ingest больше не
запускает прогрев: грузит фирму во все 4 канала (yandex/vk/mts/sms) строками
firm_channels status='loaded' (firstOrCreate — не понижает уже греющийся канал).
ch_* на заезде не пишутся (источник членства — строки firm_channels, resolveChannels
удалён). Повторный заезд обновляет только снимок-поля и НЕ сбрасывает
warmup_started_at/ready_at/stopped_at/stop_reason (заезд ≠ рестарт прогрева).
Фирма без warming-строк ни в одну заливку не идёт ⇒ автостарта нет по построению.
Запуск прогрева — кнопкой «Греть» на портале (этап C). Тесты intake мигрированы
на новую семантику. Bump baseline (+6 postJson-ложняков).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:39:45 +03:00
Дмитрий 6e771636ef feat(смс): модуль «Прогрев СМС» с заделом под мультиклиентность
Начальник отдела продаж отмечает фирмы прогрева галочками, пишет текст,
видит цену ДО отправки и журнал после. Отправки СМС в проекте не было
вообще — ни у СМС-центра (только баланс и HLR), ни у МТС (только файл).

Что сделано:
- разъём провайдера SmsProvider: новый оператор подключается одним файлом
- заглушка FakeSmsProvider — модуль работает и проверяется ДО согласования
  имени отправителя у операторов (это недели), иначе разработку не закончить
- маршрутизация по оператору: билайновский номер уходит через Билайн за
  4,75 ₽, прочие через МТС — без ручного выбора канала
- стоп-лист: кто отписался, тому не шлём никогда, проверка перед списанием
- отбор получателей с шестью причинами пропуска, все ДО траты денег
- списание скопировано с AutopodborChargeService; пока клиента нет
  (tenant_id пуст) с баланса не берём — платим оператору напрямую
- оператор номера доезжает из «Поиска клиентов» в прогрев (был известен
  и оплачен ДаДате, но терялся при передаче)

Мультиклиентность в костях: колонка tenant_id во всех четырёх таблицах
СМС с первого дня, NULL = «Лидерра сама». Клиент добавляется строкой,
а не переделкой модуля.

Найдено и закрыто при исполнении:
- замок от двойного списания стоял не на том соединении: кампания на
  pgsql_supplier, деньги на pgsql, lockForUpdate по кампании отпускался
  сразу. На бою два запуска списали бы дважды, обрыв — оставил бы пометку
  «оплачено» при неушедших деньгах. Источник правды перенесён в
  balance_transactions под замок по тенанту. Доказано тестом: старый код
  списывал 700 вместо 850
- приём в портал требовал phones строкой по regex — словарь с оператором
  получал 422, в базу не доезжало ничего. Тесты были зелёные, потому что
  звали сервис МИМО контроллера. Проверка теперь принимает оба формата,
  тест идёт через HTTP
- телефоны директоров в contacts остаются строками (договор
  SalesProspectController), словари — только в верхнем phones

Заодно вылечена мигающая поломка 48 тестов доставки лидов: помощник
createRoutingSnapshotFromProject клал снимок на сегодня, а LeadRouter
после 21:00 МСК ищет завтрашний (вечерний переворот заливки) — вечерние
прогоны падали, дневные проходили. Помощник теперь зеркалит активную дату
роутера в любой час. Регрессия SnapshotHelperTimeOfDayTest замораживает
22:00 МСК и пинит инвариант. Боевой LeadRouter не тронут.

Тесты: 84 бэкенд + фронт по экрану + 397 поисковика, весь набор 3226
зелёный, статанализ чист. Все защиты проверены вырезанием.

План: docs/superpowers/plans/2026-07-20-sms-progrev-modul.md
Спека: docs/superpowers/specs/2026-07-20-sms-progrev-modul-design.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 06:35:43 +03:00
Дмитрий 54ffba360d fix(sales): AdAudienceIntake молча ронял ch_yandex/ch_vk/ch_mts
'channels' убрали из $fillable в Task 2 плана tri-ploshadki-i-mts, но
AdAudienceIntake остался класть его в атрибуты — Laravel тихо
выбрасывал ключ при mass assignment. Итог: каждая новая фирма из
«Поиска клиентов» получала ch_yandex=false/ch_vk=false/ch_mts=false —
не грелась нигде, без единой ошибки в логах.

Контракт принимает оба формата: новые булевы поля ch_yandex/ch_vk/
ch_mts (приоритет) и старую строку channels ('yandex'|'vk'|'both')
для совместимости с ещё не переехавшей Python-службой. Если не
пришло ни то, ни другое — ch_yandex=true (фирма отправлена в прогрев
осознанно, разумное умолчание — основная площадка).

Регресс-тест ловил баг до фикса: 3/3 новых теста падали с
"Failed asserting that false is true" на ch_yandex/ch_vk.
2026-07-20 08:31:50 +03:00
Дмитрий 7d4f973733 feat(sales): площадка прогрева приходит вместе с фирмой 2026-07-19 19:33:58 +03:00
Дмитрий 566f016c9c feat(sales): в прогрев уходит фирма целиком, а не отдельный номер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:35:41 +03:00
Дмитрий 9bc587c2de fix(sales): реальный телефон директора убран из тестов и планов; клиент Яндекс.Аудиторий возвращён в main
Коммит d2c2ec43 от 19.07 оторвался от main (dangling, ни в одной ветке):
чистка ПДн и YandexAudienceClient в основную ветку так и не попали.
Номера заменены на фиктивные 7999000000X, клиент и его тесты внесены заново.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:24:21 +03:00
Дмитрий bb20a8859d @
feat(sales): приём номеров в рекламную аудиторию

Сервис-канал POST /api/sales/integration/ad-audience (тот же X-Sales-Token,
что и «Отдать менеджеру»). Новый номер добавляем, известный — продлеваем срок
из настройки и просим ночной джоб дослать (synced_at=NULL, removed_at=NULL).
Формат Яндекса строгий: 11 цифр с 7, без плюса.

Task 3 плана 2026-07-19-reklamnaya-auditoriya-kabinet-nachalnika.

Моделям дописаны @property-докблоки (конвенция проекта, как у SalesProspect) —
без них Larastan не видит полей.

Тесты: 6 в tests/Feature/Sales/AdAudienceIntakeTest.php, вся Sales-пачка 264 зелёные.

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