Commit Graph

3 Commits

Author SHA1 Message Date
Дмитрий c516e73255 refactor(смс-клиент): убран мёртвый путь сборщика аудитории и фильтр по колонке, в которую никто не пишет
Найдено при чтении кода перед Task 11, вычищено по указанию владельца
«не оставляй хвостов» (журнал В-183).

Что было. Ветка `ClientSmsAudienceBuilder::fromManual` читала снимок
получателей и отсеивала строки по `expires_at`. При этом:
· колонку `expires_at` не заполняет НИКТО — ни контроллер, ни джобы, ни
  обогащение ДаДатой; на стенде она пуста во всех строках;
· саму ветку не зовёт НИКТО: номера, вписанные руками, контроллер берёт
  прямо из запроса и разбирает единственным домом
  (`normalizeManualPhones`, строка листа 4.8).
То есть код обещал «у номера в списке есть срок годности» — поведение,
которого в портале нет. Опасность не теоретическая: я сам, начиная Task 11,
чуть не стал считать возраст снимка по этой колонке.

Что стало. Ветка убрана, на её месте ГРОМКИЙ отказ с объяснением, а не
пустой массив: пустая аудитория читалась бы как «рассылка честно ушла на
ноль номеров» — тот самый молчаливый сбой (В-121). Тест переписан на новое
поведение, мёртвый импорт убран.

🔴 Саму колонку `expires_at` НЕ удалял, и это осознанно: удаление
необратимо, а что лежит в ней на БОЕВОЙ базе, проверить нельзя — она по
умолчанию только для чтения и только с разрешения владельца. Оставлено
названным хвостом в приёмочном листе (п.30 «Чего эта работа НЕ делает»):
удаление — отдельная миграция со своим доказательством.

Проверено: ClientSms 315/315 (12 пачек, все с первой попытки), приём лидов
17/17, phpstan ровно 2 чужие давние, pint чисто. Фронт не трогался.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:13:07 +03:00
Дмитрий 1d8723f285 feat(смс-клиент): Калининград и Камчатка получают СМС каждый в своё утро
Этап 3 «Время и цена», Task 2. Строки листа 3.1, 3.2 и первая половина 3.3.

У каждой строки снимка получателей появились две вещи: часовой пояс человека и момент,
раньше которого сообщение отдавать нельзя. Считается это ОДИН раз, при создании рассылки:
снимок сильнее всего (В-39), а на 20 000 номерах пересчёт на каждом витке джоба был бы
20 000 лишних расчётов.

Три состояния строки, и их важно не путать:
  — пояс известен, ждать нечего  → отдаём прямо сейчас;
  — пояс известен, время не пришло → ждёт своего утра;
  — пояса нет                     → ждёт уточнения региона и НЕ уходит вовсе (В-85).

Пустой пояс больше не означает «шлём по Москве». Он означает «мы не знаем, где человек
живёт», и такому номеру СМС не уходит. Про него при этом НЕ пишется в журнал рассылки
«нет маршрута»: мы его даже не пробовали отправить, и врать про него нельзя.

Рассылка, у которой часть номеров ещё ждёт, получает статус «ждёт утра» вместо «готово»,
и заморозка денег с неё не снимается — смета считалась на всех, оставшимся деньги ещё
понадобятся. Счётчик отправленного при этом обновляется: он считается из журнала, то есть
всегда правда, и человеку нужно видеть «отправлено 340 из 900» сразу, а не завтра (В-94).

Регион сделки читается из subject_code, а НЕ из region_code: последний в бою не пишет никто,
а в dev там демо-значения, и мы бы считали половину страны Тюменью — молча (В-82).

Прогоны: СМС 191/191 (было 186, 5 новых тестов), приём лидов 17/17, phpstan 0, pint чисто.
Вырезанием проверено дважды: убрал проверку «пояс известен» — покраснел тест про номер без
региона; убрал проверку «время пришло» — покраснели три теста про окно. Живьём на локальной
базе: две сделки (Москва и Камчатка) плюс пять демо-сделок без региона → ушёл один москвич,
Камчатке проставлено ожидание до 22:00 UTC (10 утра её времени), пятеро ждут региона,
рассылка висит «ждёт утра». Стенд возвращён как был.

В-93: у 19 старых тестов покраснение было закономерным — у их номеров нет региона. Боевой код
не ослаблял: фикстурам проставил регион и зафиксировал время прогона, иначе тесты зависели бы
от часа запуска. ⚠️ Ветку нельзя выкатывать между этой задачей и Task 5: пока ДаДата не начнёт
давать регион всем номерам, рассылка по своей базе и по списку руками отправит ноль.

Запись схемы v9.13.
2026-07-28 15:46:53 +03:00
Дмитрий 3ec722416c feat(смс-клиент): сервисы — цена по ступеням, аудитория, отбор получателей
ClientSmsPricing (ступень по объёму получатели×сегменты, наценка не
применяется), ClientSmsAudienceBuilder (сделки/база/список, tenant-scope
SET LOCAL + явный where, нормализация телефона), ClientSmsRecipientSelector
(стоп-лист→дубль→маршрут), ClientSmsPlan DTO.

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