Files
portal/app/database
Дмитрий 36c8ced41e feat(смс-клиент): судьба сообщения отдельной графой + право служебной роли править журнал
Этап 5, Task 2. Схема v9.23 и v9.24.

Графа судьбы (миграция 101100): семь колонок у client_sms_messages —
delivery_status, delivered_at, delivery_checked_at, delivery_raw,
provider_cost, provider_parts, refunded_at, плюс индекс «кого спрашивать дальше».

Почему графа, а не переписывание status: по status считаются ДЕНЬГИ и месячный
объём клиента (ClientSmsVolumeCounter берёт ровно sent), его же складывает сторож
зависших и разбирают два словаря подписей на экране. Заменив sent на delivered,
мы обнулили бы клиенту накопление за месяц и удешевили бы цену задним числом.
Значит status остаётся фактом ПЕРЕДАЧИ, а судьба живёт рядом (В-202).

Цена оператора (provider_cost) хранится КАК ЕСТЬ: единицы поля cost у МТС
неизвестны (В-211), в рубли не переводится и ни во что не подставляется.

Право служебной роли (миграция 101200): GRANT UPDATE на client_sms_messages.
Команда опроса судьбы кросс-клиентская, ходит служебным соединением и ПРАВИТ
существующую строку — новых не пишет намеренно (В-204: новые строки делали бы
зависшую рассылку «живой» для сторожа, и деньги остались бы замороженными).
Без права на бою команда падала бы каждые десять минут; локально не видно
никогда — dev и тесты ходят суперпользователем (В-126/В-127).

Проверено:
- сторож MigrationGrantsTest расширен и проверен ВЫРЕЗОМ: гашение GRANT в
  миграции красит его;
- изоляция цела — запросом к базе после наката: RLS true/true, политика
  tenant_isolation на месте, права ролей ровно ожидаемые, все семь колонок есть;
- весь модуль 327/327 (было 320, +7 читателя судьбы), 13 пачек, все с первой
  попытки; phpstan ровно 2 чужие давние, pint чисто.
2026-07-31 06:04:56 +03:00
..