Commit Graph

1 Commits

Author SHA1 Message Date
Дмитрий a7e1dbeaf7 feat(телеграм-реклама): Этап 3.4 — опросчик вердикта модерации МТС
Этап 3 «Жизненный цикл и модерация», задача 3.4. Живая отправка кампании
ставит статус `moderating` (задача 3.2), но одобрение/отказ приходит от МТС
позже и без API. Опросчик раз в цикл робот-читалкой заходит в кабинет по
`mts_campaign_id` и применяет вердикт — КОНСЕРВАТИВНО с деньгами.

- PollTelegramModerationJob: раз в 15 минут перечисляет `moderating`-кампании
  с `mts_campaign_id` (без id на модерацию уйти не могли — пропускаем) и читает
  вердикт роботом РОВНО ОДИН РАЗ на кампанию (каждый вызов = заход в браузер):
    · `rejected` («Отклонена») → статус `rejected` + причина из кабинета; возврат
      брони кошелька (в бою, ключ совпадает с freeze); уведомление «отклонена»;
    · `approved` («Одобрена») → `launched`; уведомление «одобрена». Деньги НЕ
      трогаем — бронь под потолок держится, фактическую стоимость спишет билинг
      по факту показов (2.4 → позже);
    · `moderating` / робот не прочитал вердикт (null) → НЕ трогаем, ждём цикла.
  Кросс-тенант через pgsql_supplier (BYPASSRLS), правки под SET LOCAL
  app.current_tenant_id на дефолтном соединении; возврат брони и уведомление —
  ПОСЛЕ транзакции статуса, каждый в своём try/catch. Повторная проверка
  status===moderating под lockForUpdate (гонка). Зеркалит SweepStuckTelegramCampaignsJob.
- RobotResult: поле `moderationStatus` (approved|rejected|moderating|null) + разбор
  в fromRobotJson.
- TelegramRobotRunner: метод `readModeration($mtsCampaignId)` (режим read-status,
  Node-сторона — задача 3.5); `campaignId` проброшен в task-payload.
- NotificationService: `notifyTelegramCampaignApproved` (зеркало Rejected, in-app
  без pref-гейта) + событие EVENT_TG_CAMPAIGN_APPROVED.
- console.php: расписание опросчика everyFifteenMinutes с heartbeat-трекингом.

Селекторы экрана отказа — из живой разведки Part B (bots/mts-telegram-ads/
FLOW-FINDINGS.md, «Разведка Сессии 6»); повторного захода в кабинет не потребовалось.
Миграция не нужна — новых колонок/статусов нет (moderating/rejected уже были).

TDD. Приёмка (моя область, чистый прогон): PollModerationTest 6/6 (отклонена/
одобрена/ещё-на-модерации/не-прочиталось/без-id + деньги: при отказе бронь
возвращена, при одобрении не тронута) + весь набор ClientTg 143/143.
phpstan 0, deptrac 0, pint чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 10:18:21 +03:00