Ограничение sms.ru — не более 8 SMS за 10 минут ПО АККУНТУ суммарно, на любые
номера (антибомбинг; превышение → отказ с кодами 231/232). Таймеры first/second/
paid стартуют в одну и ту же секунду, поэтому счётчик нужно читать
последовательно, иначе каждый процесс видит «свободно 8» и вместе они
превышают лимит.
- sms_rate_limit.py: acquire_send_lock/release_send_lock на pg_advisory_lock,
lock_timeout 120с; докстринг, описание настроек и текст алерта приведены к
правилу «8 суммарно по аккаунту» (страховка «N на номер» остаётся).
- first/second/paid: блокировка берётся сразу после получения списка групп и
снимается перед выходом; если блокировку взять не удалось — прогон
пропускается (лучше пропуск, чем блокировка аккаунта), следующая попытка по
таймеру.
- Настройки: sms_limit_account_10min=8 (основное правило), окно 600с;
sms_limit_per_number_10min=8 остаётся второй страховкой.
Проверено на клоне боевой базы (sms_rl_test), реальный sms.ru не вызывался:
- 2 процесса × 20 попыток одновременно → ровно 8 отправок (4+4), далее
«аккаунт: уже 8 SMS за 600с (лимит 8)»; в БД 8 строк с sms_id в окне;
- блокировка: держатель 6с → попытка с таймаутом 2с = False (2.0с), с
таймаутом 20с = True (2.7с);
- E2E сц.3 (боевой профиль per_number=0/account=8/win=600): 8 ушло, 3
придержано; сц.1 (per-number=8) — регресс пройден.
- DB: sms_city_rules table + sms_allowed_for_city() function + RLS
- Python: фильтр sms_allowed_for_city в first_sms, second_sms, paid_storage
- Frontend: SmsCityRulesPanel — toggle on/off, период дат, комментарии
- Интеграция в SmsCampaignPanel как вкладка 'SMS по городам'
- Поддержка '*' (все города / все кампании)
- LogisticsReadinessBoard: @dnd-kit/sortable for drag-and-drop section reordering
- Drag handle (6-dot grip icon) on left of each section header
- Custom order persists in localStorage (key: logistics-section-order)
- Default: agreed → manual_required → funnel order
- Flat rows (no Button/rounded), matches OrdersTable style
- 7 columns: Клиент | Город | Тип | Дата | Водитель | Статус | Обновлён
- Horizontal scroll: overflow-x-auto + min-w-[1080px]
- Type column wide enough for Самовывоз/Доставка in one line
- OrdersTable: widened Type column (minmax 70px→100px), min-w 920→1080px
for consistent horizontal scroll across tables