Граф Wiki

Настройки: связь и сервисы

Интеграции, голосовые настройки, IPTV, личный кабинет и мобильное приложение, рассылки, персонал и доступы, лицензия и модули, обслуживание базы, гостевой портал.

Связь и сервисы: интеграции и уведомления
Содержание раздела

6.5. Интеграции

Единая точка управления внешними сервисами. Страница разделена на 8 вкладок. В нижней части любой страницы биллинга в футере отображается мини-индикатор статуса ИИ-ассистента (✓ AI / ✗ AI / ⏸ AI / ? AI) с количеством сообщений за сегодня.

Вкладка «Обзор»

Сводка всех настроенных интеграций со статусами. Цветной бейдж справа от каждой интеграции показывает: ✓ настроена / ✗ ошибка / ⏸ выключена / ? не настроена. Кнопка «Проверить все» запускает последовательно тест каждой настроенной интеграции; результаты появляются как Bootstrap-toast в правом верхнем углу.

Интеграции — обзор

Вкладка «Telegram»

Подключение Telegram-бота для двух целей: отправка уведомлений клиентам (те, кто подписался через личный кабинет) и чат оператора для системных алертов (NAS DOWN, ошибки платежей, СОРМ-выгрузка).

Интеграции — Telegram

Параметры: токен бота, прокси SOCKS5 (api.telegram.org заблокирован для российских IP), ID чата администратора, переключатели типов алертов, тест отправки сообщения.

Вкладка «VK»

Интеграция VKontakte для двух функций: OAuth-логин в ЛК (клиент может зайти через свой VK-аккаунт) и отправка сообщений в личку группы.

Интеграции — VK

Параметры: App ID для OAuth-логина, Community Token для отправки сообщений, ID группы. Тест соединения проверяет валидность токена.

Модуль «Поддержка» — единое окно техподдержки

Поддержка (/admin/support/) — нативный модуль техподдержки внутри биллинга: приём обращений из всех каналов, карточка тикета со связью с клиентом и CRM, AI-ответы, база знаний, аналитика. Весь приём и обработка обращений — внутри СмИТ Биллинг.

i-иконка рядом с заголовком раздела Поддержки и в модалках тикета ведёт прямо сюда.

Приём обращений — единая точка

Все каналы (email IMAP, VK, Telegram, MAX, голосовая почта Mango, веб-виджет) сходятся в единую функцию приёма, создающую или дополняющую тикет; AI-бот может ответить автоматически или эскалировать на оператора.

flowchart TD
    EM["Email (IMAP)"] --> IN
    VK["VK"] --> IN
    TG["Telegram (polling)"] --> IN
    MAX["MAX (WS)"] --> IN
    VM["Голосовая почта Mango"] --> IN
    WG["Веб-виджет"] --> IN
    IN["ingest_message()"] --> CONV["Тикет (SupportConversation)"]
    CONV --> AI{"AI-бот доступен?"}
    AI -- "да" --> REPLY["Автоответ"]
    AI -- "эскалация/нет" --> OP["Оператор: Входящие"]
    REPLY -. "ESCALATE_TO_HUMAN" .-> OP

Входящие: список тикетов, поиск

KPI-карточки (Активные/Ожидают/Закрыто/Спам), статусные папки, фильтры (приоритет/категория/период/ящик) и умный поиск.

Поддержка — Входящие

Поиск по тексту переписки. Ищет по теме, тексту сообщений, ФИО/договору клиента, адресу, телефону (любой формат) и №. Если совпадение не в теме — в строке тикета показывается сниппет-выдержка из тела сообщения с жёлтой подсветкой, чтобы оператор видел, почему тикет найден.

Поддержка — поиск по тексту, сниппет

Карточка тикета и сайдбар

Слева — тред сообщений и редактор ответа (шаблоны saved-replies, AI-«Нейроответ»). Справа — сайдбар: Ответственный + Приоритет, Категория, Биллинг (аватар/баланс/тариф/статус/соцсети), SLA-таймер «Ждёт ответа», Прошлые обращения клиента, Теги, Доп. поля, Логи времени.

Поддержка — карточка тикета, сайдбар

Выезжающая панель тикета

Клик по тикету из «Входящих» выдвигает панель быстрого просмотра без ухода со списка: мини-Биллинг (аватар/баланс/тариф/статус/соцсети), быстрая смена приоритета одним кликом, SLA-таймер, прошлые обращения, тред и блок ответа.

Поддержка — выезжающая панель

Мобильная адаптация

На узком экране над тредом появляется свёрнутая sticky-шапка клиента (аватар/ФИО/баланс/статус); разворот показывает SLA, договор/тариф/телефон и кнопки «Карточка» / «Управление». Ключевой контекст всегда перед глазами.

Поддержка — мобильная шапка клиента

Вложения: политика, оптимизация, архив в GCS

Категории обращений и «Перезвонить»

Категории настраиваются пер-ящик (у каждой — имя и иконка), выбираются в сайдбаре тикета и в модалке создания; фильтр по категории — в списке. Отметка «Перезвонить» на тикете создаёт заметку (с необязательным комментарием) и поднимает тикет в блок «требующие внимания».

Тикеты, требующие внимания + кнопка «следующий»

Под формой ответа — до 3 карточек тикетов, требующих внимания оператора («Перезвонить» и «Клиент ждёт N мин»); в шапке — кнопка перехода к следующему такому тикету. Включается настройкой.

Прочие возможности карточки

Настройки, база знаний, аналитика

Настройки Поддержки — вкладки (единый стиль): Общие, Авто-назначение ответственного/наблюдателя, Категории, Рейтинги, Учёт времени, IMAP+SMTP, Telegram/каналы (пер-ящик). Также: База знаний, Аналитика, Голосовая почта, Мессенджеры, Теги.

Поддержка — настройки

Вкладка «Резервный AI-провайдер»

Резервный ИИ-ассистент на случай недоступности основного. Используется те же сценарии (чат в ЛК и мобильном приложении), но через альтернативный API.

Интеграции — резервный AI-провайдер

Параметры: адрес сервера, API-ключ, переключатели мест использования (LK / Mobile / Billing-monitor). Если настроены оба — приоритет у основного.

Вкладка «IPTV» (TVIP Media + LFStream)

Подразделы вкладки для двух IPTV-провайдеров: TVIP Media (российский middleware) и LFStream «Смотрёшка». Биллинг управляет пакетами клиента через их API: при активации или деактивации соответствующей услуги в биллинге автоматически включается/выключается пакет в middleware.

Интеграции — TVIP

Интеграции — LFStream

Параметры обоих сервисов: URL API, login/pass или API-ключ, ID оператора, тест соединения. Маппинг услуг → пакетов задаётся отдельно в разделе 6.6 IPTV-пакеты.

Вкладка «GCS» (Google Cloud Storage)

Google Cloud Storage используется для публичных файлов: скриншоты документации, иконки услуг, отчёты для скачивания.

Интеграции — GCS

Параметры: Service Account JSON-ключ (загружается через файл или вставляется как текст), имя GCP-проекта, имя бакета, префикс для путей внутри бакета.

Вкладка «География» (Адреса + Карта)

Объединяет два геосервиса: геокодер DaData (поиск адреса с автокомплитом и автоматический расчёт координат) и тайловый слой карты (OpenStreetMap / 2ГИС / Yandex Maps). Используется в карте клиентов и при создании адреса.

Интеграции — Адреса

Интеграции — Карта

Параметры DaData: API-ключ. KPI-блок показывает сколько адресов уже геокодировано из общего числа (на демо: 4045 / 4261 = 94.93%).

6.5А. Голосовая связь (провайдеры телефонии)

URL: /admin/settings/telephony/ · Модуль: voice «Голосовая связь» (тариф Pro и выше) — приём звонков операторами: голосовая почта, click-to-call, записи разговоров → тикеты Поддержки и сделки CRM.

⚠ Не путать с модулем «IP-телефония». Модуль telephony («IP-телефония») — это оказание услуги связи клиентам (номера, SIP-учётки, тарифы, CDR). Модуль voice («Голосовая связь») — внутренний инструмент операторов: через какого оператора мы сами принимаем и совершаем звонки.

Оператор телефонии — плагины

Оператор подключается как плагин: базовый интерфейс TelephonyProvider в billing/services/telephony_providers.py. Новый оператор = новый класс в реестре, страница настроек не меняется. Активный провайдер хранится в настройке TELEPHONY_PROVIDER.

ПровайдерКлючиПроверка связиClick-to-call
Mango Office (mango) MANGO_API_KEY, MANGO_API_SALT, MANGO_API_URL vPBX API, подпись sha256(key+json+salt) callback: оператор → клиент
Novofon (novofon) ASTERISK_ARI_URL, ASTERISK_ARI_USER, ASTERISK_ARI_PASSWORD, ASTERISK_TRUNK_ENDPOINT, ASTERISK_CALLERID Asterisk ARI GET /asterisk/info ARI originate через PJSIP-транк

Карточка провайдера показывает бейджи «настроен / click-to-call / записи». Кнопка «Проверить связь» делает реальный запрос к API оператора и возвращает результат прямо на странице.

Голосовая почта — маршрутизация линий

Оператор присылает запись звонка письмом на служебный ящик. По номеру, на который позвонил клиент (VoiceMailLine.phone), выбирается:

Расшифровка звонка нормализуется AI и классифицируется: обращение в поддержку, заявка на подключение или мусор. Клиент определяется по номеру — если номер принадлежит клиенту, всегда создаётся тикет (не лид).

Пропущенные звонки → «Неразобранное»

Тумблер на странице включает настройку CRM_UNSORTED_MANGO: пропущенный входящий (и невнятное голосовое) создаёт сделку в воронке «Неразобранное» с дедупликацией по телефону — обращение не теряется, оператор разбирает его вручную. Запись разговора прикрепляется к сделке (DealCall.recording_gcs_url).

Журнал голосовой почты: /admin/support/voicemail/ — разбор писем оператора, тест разбора без создания тикета (dry-run) и лог событий.

6.6. IPTV-пакеты

Маппинг услуг биллинга на IPTV-пакеты провайдера. Например: услуга «IPTV Базовый» в биллинге → пакет ID 12 в TVIP, услуга «IPTV Премиум» → пакет ID 18. При активации/деактивации услуги клиента биллинг автоматически включает/выключает соответствующий пакет в middleware.

IPTV-пакеты

Встроенный просмотр ТВ в ЛК и приложении

Клиент с активной IPTV-подпиской может смотреть каналы прямо в личном кабинете и в мобильном приложении — без установки стороннего приложения провайдера. В ЛК это HTML5-плеер (нативный HLS в Safari/iOS или через hls.js в остальных браузерах), в приложении — нативный video_player (ExoPlayer на Android, AVPlayer на iOS).

ЛК — Интерактивное ТВ с просмотром

Плейлист каналов формируется по шаблону URL провайдера с подстановкой {login} / {account} клиента из таблицы маппинга (IptvAccountMapping). Просмотр доступен только при выполнении всех условий:

Если условие не выполнено, клиенту показывается понятная причина («Просмотр доступен только в сети СмИТ», «Плеер каналов настраивается» и т.п.).

НастройкаНазначение
IPTV_WATCH_ENABLEDВключить встроенный просмотр (по умолчанию выкл)
IPTV_WATCH_REQUIRE_ONLINEСмотреть только при активной RADIUS-сессии
IPTV_PLAYLIST_TEMPLATE_TVIPШаблон URL плейлиста TVIP, плейсхолдеры {login} {account}
IPTV_PLAYLIST_TEMPLATE_LFSTREAMШаблон плейлиста «Смотрёшка»
IPTV_TVIP_APP_URL / IPTV_LFSTREAM_APP_URLСсылки на приложения провайдеров

Реализация: сервис billing/services/iptv_playlist.py (can_watch / playlist_url / watch_context), ЛК /lk/iptv/, мобильный экран «Интерактивное ТВ» (GET /mobile-api/v1/iptv/packages возвращает блок watch).

6.6А. Модуль IPTV — раздел /admin/iptv/

Отдельный раздел сайдбара IPTV объединяет управление интеграцией с TV-провайдерами (TVIP Media и LFStream «Смотрёшка»): дашборд, пакеты, аккаунты клиентов, журнал API и настройки.

Модуль IPTV — обзор

Дашборд

Бизнес-метрики: выручка IPTV (MRR — сумма активных подписок UsersUsluga с system_type=21), число активных подписок и витринных пакетов, динамика подключений/отключений за 30 дней, здоровье интеграции (% успешных запросов к провайдерам за сутки), топ-пакеты, распределение по провайдерам.

Дашборд IPTV

Пакеты и витрина провайдеров

Витрина-справочник популярных РФ-провайдеров: TVIP Media и LFStream — с рабочей интеграцией, остальные (24часаТВ, LIME HD, Ministra, Микроимпульс, Смартила) — справочно. Ниже — таблица маппинга «услуга биллинга ↔ пакет провайдера» с автоподбором по имени и подсветкой несопоставленных строк.

Пакеты IPTV

Аккаунты клиентов

Список связок «клиент ↔ аккаунт у провайдера» (IptvAccountMapping). Чипы-фильтры по статусу (Все / Подтверждённые / Не подтверждённые / С ошибкой), колонки Пакет и Услуга (берутся из БД через активные IPTV-услуги клиента, без онлайн-опроса провайдера).

Аккаунты IPTV

Клик по кнопке «Подробнее» открывает модалку деталей аккаунта в три колонки — статус, синхронизация, аккаунт/логин, договор, тариф, баланс, блокировки + IPTV-услуги клиента и журнал запросов к провайдеру. Клик по имени клиента открывает слайд-панель (см. ниже).

Детали аккаунта

Подтверждение аккаунта (account_verified): ресинк находит аккаунт у провайдера по логину; только для подтверждённых аккаунтов реально выполняются block/unblock (гейт защищает от массовых ложных ошибок). В TVIP-панели ~127 аккаунтов, из них принадлежат СмИТ — 16.

Журнал API и настройки

Журнал всех запросов к провайдерам (IptvApiLog) с человекочитаемым действием и телом ошибки. Настройки — credentials TVIP/LFStream, тест подключения, биллинг-toggle.

Настройки IPTV

Биллинг IPTV и kill-switch

IPTV-подписка — обычная услуга клиента (UsersUsluga, system_type=21), списывается billing_worker штатно (1-го числа). В настройках есть переключатель «Не списывать деньги за IPTV» (IPTV_BILLING_DISABLED) — режим тестирования: плата за IPTV не списывается, дата следующего списания сдвигается вперёд (задним числом долг не образуется), провижининг у провайдера продолжает работать. Блокировка IPTV при долге — опциональна (IPTV_BLOCK_ON_NEGBAL, по умолчанию выкл).

Привязка пакета в карточке услуги

В модалке услуги типа «IP телевидение» есть вкладка IPTV — выбор провайдера и пакета. Связь «услуга ↔ пакет» можно задавать прямо из карточки услуги (не только на странице Пакеты).

Вкладка IPTV в услуге

На карточке клиента

На вкладке «Услуги» клиента в колонке «Примечание» для IPTV-услуги показывается «📺 провайдер: пакет», для трафика — «⚡ N Мбит/с» (макс. скорость CEIL_IN). У IPTV-услуги — бейдж «провайдер», открывающий модалку диагностики (сводка/пакеты/аккаунты/журнал).

Примечания услуг клиента

Слайд-панель клиента: блок «Услуги» и IPTV-сайдбар

В слайд-панели клиента (открывается кликом по имени) после блока «Последняя сессия» добавлен аккордеон «Услуги» — список услуг с иконкой по типу, примечанием (провайдер/скорость) и ценой. При открытии со страницы аккаунтов аккордеон автоматически разворачивается.

Слайд-панель: блок Услуги

У IPTV-услуги — кнопка «Провайдер», открывающая поверх вложенный IPTV-сайдбар (сводка, активные пакеты, аккаунты у провайдеров с отметкой «подтверждён», журнал запросов).

IPTV-сайдбар

6.7. ЛК и мобильные

Настройки личного кабинета и мобильных приложений. Страница в админке разделена на 5 вкладок.

Вкладка «Обзор»

Сводка статуса ЛК: количество активных клиентов в личном кабинете за месяц, количество установок мобильного приложения, ссылки на сторы (Google Play / App Store), ссылка на сам ЛК и на админ-страницы HelpDesk.

ЛК и мобильные — обзор

Вкладка «Брендинг»

Фирменное оформление ЛК: логотип, цвет акцента, доменное имя (lk.example.ru), favicon, OG-теги для соцсетей. Эти данные применяются к ЛК отдельно от админки — у админки и у ЛК могут быть разные цвета и логотипы.

ЛК — брендинг

Вкладка «Функции»

Toggle-переключатели видимости разделов в ЛК. Каждый переключатель — это глобальное правило, видит ли его клиент. Например, выключив «Тариф», оператор скрывает у всех клиентов возможность сменить тариф самостоятельно.

ЛК — функции

Контролируемые функции: видимость тарифов и их смена, разрешение менять MAC-адрес, HelpDesk, история операций, обещанный платёж, реферальная программа, чат-AI и др.

Вкладка «Безопасность»

Параметры доступа в ЛК: разрешать ли вход только из сети оператора (когда клиент дома), автологин по IP без логина и пароля, запрет смены пароля клиентом (полезно при общем семейном договоре).

ЛК — безопасность

Вкладка «Мобильное»

Настройки мобильных приложений Android и iOS: текущая версия в сторе, минимальная версия для force-update (если клиент использует более старую — увидит диалог обновления), ссылки на стора, параметры push-уведомлений (Firebase).

ЛК — мобильное

Подробная документация — на отдельной странице

Описание возможностей ЛК, авторизация (Telegram/VK), настройки интеграций, AI-ассистент, push (Firebase), мобильные приложения Android и iOS — в разделе ЛК и мобильные.

Открыть документацию ЛК

6.8. Сообщения

Настройка каналов отправки уведомлений клиентам. Страница разделена на 5 вкладок.

Вкладка «Email (SMTP)»

Параметры SMTP-сервера для исходящей почты: «От кого» (From), хост, порт, логин/пароль, режим шифрования, проверка SSL-сертификата. Кнопка «Отправить тест» отправляет письмо на указанный адрес через текущие несохранённые значения формы — это позволяет проверить настройки до сохранения.

Email — настройки SMTP с radio-группой шифрования

Шифрование SMTP — radio-группа

Тип шифрования задаётся одной radio-группой из трёх взаимоисключающих кнопок — включить два режима сразу нельзя:

Авто-подстановка порта. При смене radio JS подставляет порт (587/465/25), если в поле сейчас стоит один из «дефолтных» портов. Если оператор задал кастомный порт (например, 2525) — он сохраняется.

Защита на back-end. Если из формы каким-то образом пришли email_use_tls=True и email_use_ssl=True одновременно (старый кэш браузера, legacy POST), email_test_send и email_settings_save применяют XOR-нормализацию: оставляют тот режим, который соответствует порту (465 → SSL, иначе STARTTLS), второй гасят. В SystemSettings теперь никогда не лежат оба True.

Поле «От кого» и политика отправителя

Большинство SMTP-серверов (включая SmitMailServ) применяют политику «отправитель должен совпадать с авторизованным пользователем». Если у вас логин noreply@вашдомен.ru, но в поле «От кого» вы указали billing@вашдомен.ru — сервер вернёт ошибку:

SMTPRecipientsRefused: 553 5.7.1 <billing@вашдомен.ru>:
Sender address rejected: not owned by user noreply@вашдомен.ru

Решения:

Вкладка «SMS»

Выбор провайдера SMS из четырёх: SMSAero, SMSC.ru, SMS.ru, Свой шлюз (Android-телефон). Каждый со своими параметрами (логин/пароль или API-ключ), общий — имя отправителя. Тест отправки на тестовый номер.

Сообщения — SMS

Свой шлюз — СмИТ SMS Gate for Android

Установите приложение СмИТ SMS Gate for Android на телефон с SIM-картой и следуйте инструкциям прямо в приложении (мастер первого запуска подготовит всё за 1–2 минуты).

Старый Android-смартфон с SIM-картой превращается в локальный SMS-шлюз биллинга. Стоимость — только тариф вашего мобильного оператора (часто пакеты «безлимитный SMS» 200–300 ₽/мес). Приложение — re-skin форк open-source SMS Gateway for Android (capcom6, Apache-2.0) под брендом СмИТ; см. MobileApp/SmitSMSGateway/ в репозитории биллинга.

Поддерживается 2 режима:

Настройка телефона (Cloud-режим)
  1. Установить приложение СмИТ SMS Gate for Android на телефон с SIM-картой.
  2. На главном экране — переключатель «Облачный сервер» → ON.
  3. Скопировать Имя пользователя и Пароль с экрана.
  4. Включить Автозапуск (внизу экрана).
  5. В настройках Android: Батарея → СмИТ SMS Gate → «Без ограничений» (иначе уйдёт в фоновую заморозку).
  6. Внизу должна гореть надпись ОНЛАЙН — телефон подключён к cloud.
Настройка биллинга
ПолеЗначение
ШлюзСвой шлюз (Android-телефон)
Имя пользователязначение из приложения (например F14JMP)
Парользначение из приложения
URL шлюзаLocal: http://192.168.1.50:8080
Cloud: https://api.sms-gate.app/3rdparty/v1
Номер SIM1 или 2 (для двухсимочников)

После сохранения — поле «Тестовая отправка» внизу страницы. Введите свой номер, нажмите Отправить тест. Должно прийти SMS в течение 5–30 секунд.

Webhook статуса доставки

Только для cloud-режима sms-gate.app. Биллинг получает подтверждение реальной доставки SMS клиенту (не «отдано в шлюз», а «доставлено на телефон оператора»). Без webhook'а биллинг знает только что POST в шлюз вернул 200 OK — между «принято cloud'ом» и «дошло до пользователя» проходит от секунд до часов (если телефон-шлюз оффлайн).

Настройка (4 шага)
  1. В UI /admin/settings/messaging/sms/ при выборе шлюза «Свой шлюз (Android-телефон)» внизу появляется блок «Webhook delivery-status».
  2. Скопировать URL для регистрации кнопкой «📋 Копировать» (формат https://<ваш-биллинг>/api/sms/webhook/sms-gate/).
  3. Нажать «🎲 Сгенерировать» для случайного 32-символьного hex-ключа HMAC-SHA256. Сохранить настройки.
  4. Регистрация webhook'ов в sms-gate.app — через REST API (в приложении на телефоне нет UI для этого):
    curl -u USERNAME:PASSWORD https://api.sms-gate.app/3rdparty/v1/webhooks \
      -H 'Content-Type: application/json' \
      -d '{"url":"адрес вашей установки/api/sms/webhook/sms-gate/",
           "event":"sms:delivered","signingKey":"<ваш-secret>"}'
    Повторить для всех 3 событий: sms:sent, sms:delivered, sms:failed. Каждый зарегистрированный webhook возвращает свой ID.
Жизненный цикл SMS
ШагЧто происходитПоле MsgStack
1. Биллинг → cloudPOST /3rdparty/v1/messages возвращает {"id": "...", "state": "Pending"}sms_external_id = id, sms_status = pending
2. Cloud → телефонWebSocket-push на телефон-шлюз (если ОНЛАЙН)не меняется
3. Телефон → операторSMS отправлено в сеть мобильного оператораwebhook sms:sentsms_status = sent
4. Оператор → клиентSMS доставлено на телефон клиентаwebhook sms:deliveredsms_status = delivered, sms_status_date = now()
4'. ОшибкаНет связи у телефона / номер заблокирован / денег нетwebhook sms:failedsms_status = failed, sms_error = код ошибки

Реальная статистика (с рабочего сервера 2026-05-07): pending → sent → delivered за 4–6 секунд при онлайн-телефоне. Если телефон оффлайн — sms-gate.app ставит сообщение в очередь и доставляет когда телефон вернётся (тестировано: 11 минут оффлайна — webhook'и пришли retry'ями сразу как телефон поднялся).

HMAC-подпись

Sms-gate.app шлёт подпись по схеме (официальная docs):

X-Signature: hex(HMAC-SHA256(secret, body + X-Timestamp))
X-Timestamp: 1715091045   # Unix epoch

Биллинг проверяет подпись через billing/views/sms_webhook.py::_verify_signature с поддержкой 4 форматов (для совместимости):

  1. HMAC(body+timestamp, secret) hex — официальный формат sms-gate.app
  2. HMAC(body, secret) hex — legacy без timestamp
  3. base64 любого из вышеперечисленных
  4. С префиксом sha256= или hmac-sha256= (стандартные конвенции webhook)

Constant-time сравнение для каждого кандидата. Если SMS_GATE_WEBHOOK_SECRET в SystemSettings пустой — подпись не проверяется (для теста / отладки). В production обязательно задавайте secret.

Защиты
От подделки
HMAC-SHA256 с секретом из SystemSettings.SMS_GATE_WEBHOOK_SECRET. Невалидная подпись → HTTP 401 + лог с диагностикой (sig, ts, body_len, headers).
От downgrade статусов
Если уже delivered, повторный event sms:sent не сбросит обратно. Приоритет: pending=0 < sent=1 < failed=2 < delivered=3. Учитывается только апгрейд по rank.
От Denial-of-Service
Endpoint работает без auth (стандарт webhook'ов), но без подписи или с невалидной — атакующий может только обновить sms_status для уже существующих sms_external_id. Записи не создаются, FK не нарушаются. Реальный риск — флуд логов, лечится rate-limit на nginx (опционально).
От retry-flood
Sms-gate.app шлёт webhook несколько раз для одного события (retry'ит при failure). Биллинг идемпотентен: повторный webhook на тот же messageId+status — no-op (запись уже обновлена с правильным rank).
Endpoint

URL: POST /api/sms/webhook/sms-gate/ (публичный, CSRF-exempt).

Headers (от sms-gate.app):

Body (JSON):

{
  "deviceId": "fIuD_aVrUyAIuN7DhxDw8",
  "event": "sms:delivered",
  "id": "<webhook-event-id>",
  "payload": {
    "messageId": "8_EwH18QDve2RnA_Sx7ye",   // совпадает с MsgStack.sms_external_id
    "phoneNumber": "+79991234567",
    "deliveredAt": "2026-05-07T14:13:50.923+03:00",
    "errorCode": "..."  // только для sms:failed
  },
  "webhookId": "..."
}

Responses:

UI отображения статуса

На карточке клиента → вкладка «Сообщения» (8.16) бейдж SMS теперь показывает реальный статус доставки:

БейджИконкаСтатусTooltip
SMSfa-check-doubledelivered«Доставлено в DD.MM HH:MM:SS»
SMSfa-checksent«Отправлено оператору в DD.MM HH:MM:SS»
SMSfa-clockpending«Ожидает доставки (id: внешний_id)»
SMSfa-timesfailed 🚫«Ошибка доставки: текст ошибки»
SMSlegacy (без webhook)«SMS отправлено (без webhook delivery-status)»

JS-функция _smsBadge(msg) в send_message_tab.html строит бейдж динамически после AJAX-create. MutationObserver на #msg-history-tbody автоинициализирует Bootstrap-tooltips на новых строках. После отправки бейдж 🟡 pending → через несколько секунд webhook → 🔵 sent → 🟢 delivered. Авторефреша без F5 пока нет (планируется в одном из обновлений — JS-poll на /sms_status/<msg_id>/).

Решение проблем
SMS уходит, но в БД sms_status='pending' навсегда
Webhook'и не зарегистрированы в sms-gate.app. Проверь GET /3rdparty/v1/webhooks с твоей auth — должно быть 3 записи (sent / delivered / failed).
Webhook прилетает, но в логах invalid HMAC signature
Secret в биллинге и в webhook-регистрации не совпадают. Удали webhook и зарегистрируй заново с правильным secret. Или временно очисти secret в UI — webhook начнёт работать без проверки подписи (для теста).
SMS отправляется, но в БД sms_external_id=''
Используется тестовая отправка из UI (кнопка «Отправить тест») — она шлёт напрямую через _send_sms_tecno без создания MsgStack. Это by design. Для реального теста с tracking — отправь сообщение через карточку клиента → вкладка «Сообщения».
Webhook прилетает, но MsgStack не обновляется ({"matched": false})
Не совпадает messageIdsms_external_id. Проверь что биллинг сохраняет id из ответа sms-gate.app: SELECT pk, sms_external_id FROM msg_stack WHERE sms_done=true ORDER BY id DESC LIMIT 5.
DNS-резолюция падает (NameResolutionError 'api.sms-gate.app')
Известная нестабильность Docker-DNS. В docker-compose.prod.yml блоки web и celery должны содержать extra_hosts: - "api.sms-gate.app:94.241.170.26". После добавления — recreate контейнеров.
Подтверждено вживую

(2026-05-07) на рабочем сервере, клиент Письменный Павел (#7686, +79177271796):

14:13:44  Биллинг → POST api.sms-gate.app/3rdparty/v1/messages
          MsgStack #20697.sms_external_id = "8_EwH18QDve2RnA_Sx7ye"
          MsgStack #20697.sms_status = 'pending'
14:13:46  cloud → телефон ОНЛАЙН → отправка SMS
14:13:48  оператор → +79177271796 (SMS доставлена)
14:13:50  cloud → POST webhook event=sms:delivered → HMAC ✓
          MsgStack #20697.sms_status = 'delivered'
          MsgStack #20697.sms_status_date = 2026-05-07 14:13:50

Полная цепочка от отправки до подтверждения доставки в БД — 6 секунд. UI обновляется при F5 страницы.

Вкладка «Telegram»

Отправка уведомлений клиентам через Telegram-бота. Условие — клиент должен один раз связать свой Telegram-аккаунт с учётной записью в личном кабинете (нажав кнопку «Привязать Telegram»).

Сообщения — Telegram

Бот используется тот же, что и в разделе Интеграции → Telegram. На этой вкладке только переключатели: посылать ли уведомления через Telegram и какие именно типы.

Вкладка «Push»

Firebase Cloud Messaging для push-уведомлений в мобильном приложении. Параметры: путь к Service Account JSON-файлу Firebase, имя проекта, переключатели типов событий.

Сообщения — Push

Вкладка «Шаблоны»

Текстовые шаблоны уведомлений с переменными подстановки. Каждое событие имеет свой шаблон и список включённых каналов. Например, событие «Низкий баланс» может отправляться по Email + SMS, а «Подключение услуги» — только по Push.

Сообщения — шаблоны

Поддерживаемые события (19):

Доступные переменные — полный список, других подстановок нет: %(abonent_name)s — ФИО клиента, %(contract_number)s — номер договора, %(balance)s — текущий баланс, %(currecy)s — валюта, %(tarif_name)s — тариф клиента, %(operator_name)s — название оператора, %(text)s — дополнительный текст (услуга, сумма), %(psw_token)s — проверочный код. При включённом флаге «Jinja» те же имена пишутся как {{ abonent_name }}.

Редактор шаблонов: Plain + HTML

Модальное окно редактирования шаблона расширено: 2 вкладки«Текст (plain)» для коротких сообщений (SMS / Push / Telegram / VK / ЛК) и «HTML (Email)» с WYSIWYG-редактором TinyMCE 6 для оформленных HTML-писем. Plain-текст используется для всех каналов кроме Email; для Email — берётся HTML, если он заполнен, иначе fallback на plain (с заменой переносов на <br>).

Редактор шаблонов: вкладка HTML с TinyMCE

Возможности

Редактор шаблонов: вкладка Plain-текст

Кликабельные переменные

В правой панели модалки — справочник переменных в двух форматах: Python format (%(name)s) и Jinja2 ({{ name }}, при включённом флаге «Jinja»). Каждая переменная обёрнута в кликабельный <code>: hover подсвечивает её зелёным, клик — вставляет в позицию курсора. Если активна вкладка HTML — вставка идёт в TinyMCE через insertContent(); если plain — в обычное <textarea>.

Подсветка переменной при наведении (зелёный фон)

Какой канал берёт что
КаналИсточникПояснение
SMStxt (plain)HTML обрезается до текста (см. SMS webhook для счётчика сегментов).
Push (FCM)txt (plain), 200 симв.HTML не поддерживается push-сервисами.
Telegramtxt (plain)Базовое форматирование Telegram через future-расширение.
VKtxt (plain)Сообщения сообщества — только текст.
ЛКtxt (plain)Notification banner в личном кабинете.
Emailhtml_body если заполнен,
иначе txt с заменой \n<br>
Один шаблон — два канала. Plain-fallback гарантирует совместимость со старыми шаблонами.
Готовые HTML-шаблоны писем

В toolbar визуального редактора добавлена кнопка «Шаблоны писем» (зелёная, с иконкой ). Клик открывает выпадающий список из 8 готовых пресетов с автоматической подстановкой брендинга компании (CompanyBranding) — название, логотип, ИНН/КПП, контакты, адрес, акцентный цвет.

Dropdown «Шаблоны писем» с 8 пресетами

ПресетСценарий использования
Чистый шаблонТолько шапка + подвал с брендингом, заголовок и заглушка для текста
Платёж полученПодтверждение зачисления платежа с указанием баланса (зелёный card-style)
Низкий балансПредупреждение + CTA-кнопка «Пополнить счёт» (акцентный цвет)
Услуги приостановленыБлокировка по балансу + кнопка восстановления
Услуга подключенаУведомление о новом подключении (с балансом)
Технические работыИнформация о плановых работах (orange-style)
Поздравление с ДРПраздничное письмо с центрированной вёрсткой
Восстановление пароляПисьмо с проверочным кодом (моноширинный шрифт, увеличенный текст)

Каждый пресет — полноценное Email-письмо с тремя секциями:

Пресет «Низкий баланс» — вставленное письмо с брендингом

Поведение при вставке:

Endpoint'ы:

Реализация: billing/services/email_presets.py — изолированный модуль с реестром PRESETS. Каждый пресет — функция-сборщик контента, обёрнутая в общий wrapper _wrap() с шапкой и подвалом из _branding_dict(). Добавление нового пресета = одна функция + запись в PRESETS.

Миграция и схема

Поле AdminMsg.html_body (TextField, nullable) добавлено (миграция 0120_adminmsg_html_body). Существующие шаблоны не затронуты — у них html_body = NULL, Email-канал продолжает работать через txt. Заполнить HTML можно постепенно по мере необходимости (приветствие новому клиенту → красивая HTML-вёрстка, авто-уведомление о низком балансе → plain).

6.9. Персонал и доступ

Управление сотрудниками биллинга. Доступно только пользователям с правами root. Страница разделена на 4 подраздела.

Обучающее видео 2:55

Чем группа доступа отличается от роли интерфейса, почему «не отмечено — значит запрещено» и что происходит с уволенным сотрудником.

1920×1080 с озвучкой Скачать

Подраздел «Персонал»

Список сотрудников с фильтрами «Активные» / «Архив». Уволенный сотрудник переводится в архив (не удаляется), его действия в аудите остаются. Auto-аватары с инициалами на цветном градиенте (хеш от имени).

Персонал — список

Карточка сотрудника содержит: ФИО, e-mail, телефон, должность, дату приёма, группы доступа, настройки уведомлений. Дополнительно у каждого сотрудника есть Профиль — личные настройки (аватар, избранные отчёты, журнал собственных действий).

Подраздел «Интерфейс»

Настройка видимости полей в формах для каждой роли. Поля можно скрыть от группы, сделать read-only или required. Например, кассиру оставить только поле «Сумма платежа», а инженеру — все поля кроме финансовых.

Персонал — настройка интерфейса

Подраздел «Доступ к моделям»

Какие модели биллинга видит каждая группа пользователей. Чекбоксы Read / Create / Update / Delete для каждой модели (Tarif, Usluga, Abonents, NAS, и т.д.). Без галочки Read — пункт меню скрывается у этой группы.

Персонал — доступ к моделям

RBAC разделов админки

Обычные модели (Тариф, Услуга и т.п.) гейтятся чекбоксами выше. Но часть разделов — CRM, лендинги, настройки — это не модели, а отдельные страницы. Для них на странице группы есть блок «Разделы админки»: отметьте, какие разделы доступны этой группе.

Политика — deny-by-default: раздел из этого списка виден в меню и открывается по ссылке только у групп, где он отмечен (плюс суперпользователь — всегда). Без отметки раздел скрыт в меню и запрещён даже по прямой ссылке — открывается страница «Доступ к разделу запрещён».

flowchart TD
  A[Запрос к разделу] --> B{Суперпользователь?}
  B -- Да --> OK[Доступ разрешён]
  B -- Нет --> C{Раздел в списке
«Разделы админки»?} C -- Нет --> OK C -- Да --> D{Есть отметка
у группы пользователя?} D -- Да --> OK D -- Нет --> DENY[403 «Доступ запрещён»
+ пункт скрыт в меню]

Блок «Разделы админки» в правах группы

Дефолтное распределение, засеянное при внедрении (суперпользователь и Администратор + Директор — во всех разделах):

РазделКому доступен (кроме su, Администратора и Директора)
Salesbot / Рекламные кампании / ЛендингиМаркетинг
CRM: Мастер, Воронки, Доп. поля, Голосовая почта · Поддержка → НастройкиСтарший менеджер
CRM → Настройки
Настройки: Система, Интеграции, Лицензия, ЛК, AI
Настройки → Финансы (платежи/фискал/банк)Бухгалтер
Настройки → Инфраструктура (почта/DNS/бэкапы)только Администратор
Настройки → СообщенияСтарший менеджер
Настройки → МаркетингМаркетинг
Клиентывсе операционные роли + Маркетинг (кроме Кладовщика)
ТарификацияСт.менеджер, Менеджер, Ст.сэйлс, Бухгалтер, Отчётность, Маркетинг
CRM (воронка/задачи/карта)менеджеры, сэйлсы, Монтажник, Отчётность, Маркетинг
Поддержка (входящие/тикеты)Ст.менеджер, Менеджер, Отчётность
ОборудованиеМонтажник, Отчётность
СправочникиСт.менеджер, Менеджер, Бухгалтер, Монтажник
Отчётыменеджеры, Ст.сэйлс, Бухгалтер, Отчётность, Маркетинг
ДокументыСт.менеджер, Менеджер, Бухгалтер
Видеонаблюдениеменеджеры, сэйлсы, Монтажник, Отчётность, Маркетинг
IPTV / IP-телефонияСт.менеджер, Менеджер, Отчётность
СкладКладовщик, Монтажник, Отчётность

Если у роли нет доступа к разделу, при переходе по ссылке открывается понятная страница:

Страница «Доступ к разделу запрещён»

Аварийный выключатель

Настройка SECTION_RBAC_ENABLED = 0 мгновенно отключает весь RBAC разделов (доступ ко всем разделам открывается всем сотрудникам) — на случай, если какая-то роль оказалась заблокирована ошибочно. Точечно доступ правится тут же, галочками в блоке «Разделы админки».

Подраздел «Доступ к клиентам»

Какие папки клиентского дерева видит каждая группа. Полезно для разделения филиалов: агент филиала «Север» видит только папку «Север», агент филиала «Юг» — только папку «Юг», главный администратор видит весь корень.

Персонал — доступ к клиентам

6.10. Лицензия

Информация о действующей лицензии: количество разрешённых клиентов, дата окончания, тип (коммерческая / тестовая / OEM).

Лицензия

Подробная документация — на отдельной странице

Порядок поставки и продления, варианты оплаты, типичные проблемы — в разделе Лицензирование.

6.10А. Модули и расширения

URL: /admin/settings/modules/ — каталог модулей биллинга: что включено, что доступно в вашем тарифе и что можно докупить.

Карточка модуля

Каждый модуль показан карточкой с обложкой, номером версии (в правом верхнем углу), кратким описанием и чипами тарифов, в которые он входит. Управление:

Базовые модули. Ядро биллинга, Интернет-доступ (RADIUS), Отчёты и Карта сети помечены как «Базовый» — они входят во все тарифы, всегда включены и не переключаются. Поэтому в счётчике «Включено N / доступно N / всего» базовые не учитываются.

Недоступные модули

Модули, не входящие в ваш тариф, отображаются полупрозрачными с приглушённой обложкой, замком и подписью «Доступно в тарифе: …». Их карточки видны — чтобы понимать, что даёт переход на старший тариф. Ссылка «Перейти на тариф →» ведёт в раздел Лицензия.

Окно «Подробнее»

Открывает карточку модуля как в маркетплейсе: обложка, строка «категория · разработчик · тарифы» с кнопкой «Документация», полное описание и четыре вкладки:

Пустые вкладки скрываются автоматически. Содержимое карточек приходит с сервера лицензий и кэшируется на 6 часов — при недоступности сервера карточки продолжают работать, но без обложек и описаний.

Зависимости. Некоторые модули не работают в одиночку: например, «Голосовая связь» опирается на «Поддержку» и «CRM» (звонок превращается в тикет или сделку), «Автообзвон» — на «Голосовую связь», а «AI-ассистент» — на «Поддержку» для эскалации.

Виджеты-плагины

Рядом с модулями — каталог виджетов (/admin/settings/widgets/): небольшие расширения, которые встраиваются в дашборды, карточку клиента, карточку сделки и сайдбар тикета. Виджет привязан к модулю и доступен, только если этот модуль включён.

6.11. Резервные копии

Резервные копии — самостоятельный таб в группе «Обслуживание».

Резервные копии — конфигурации и список копий

Типы копий:

Настройка: имя конфигурации, тип, расписание (cron-выражение), количество хранимых копий, включить/выключить, кнопка «Сделать сейчас» (запуск вне расписания).

Список копий — записи с датой, размером, статусом, типом. Кнопки в строке: Скачать, Восстановить, Удалить. В процессе backup'а строка показывает анимированный прогресс-бар (опрос статуса каждые 3 секунды).

Защита от «зависших» копий: если Celery-воркер падает или перезапускается во время резервного копирования (например, при переезде сервера), запись могла навсегда остаться в статусе running — на странице крутился вечный спиннер и шёл бесконечный опрос статуса. Теперь функция _reap_stale_running() автоматически помечает любую запись в running старше 30 минут как «Ошибка» с понятным сообщением. Срабатывает при открытии страницы и при каждом опросе статуса (раз в 3 секунды), так что зомби-запись сбрасывается сама. Дополнительно run_backup теперь явно завершается ошибкой при неизвестном типе бекапа или если файл не был создан.

Восстановление — модалка с выбором: только БД / только файлы / полное восстановление. Для типа docker показывается инструкция как развернуть копию на новом сервере.

6.12. Очистка БД и сессий

URL: /admin/settings/db_cleanup/ (раздел «Обслуживание», только root-доступ).

Раздел централизованного автоматического обслуживания БД и борьбы с «зомби»-сессиями RADIUS. Состоит из 5 блоков (сверху вниз): Состояние БД, Автоочистка, История запусков, Аудит зомби-сессий, Выбор NAS для аудита.

Очистка БД — общий вид и состояние

Состояние БД сейчас

Верхний блок — реальный счётчик записей в 4 ключевых таблицах (обновляется AJAX):

Каждая ячейка показывает размер (Кб/Мб/Гб) + количество записей. Если рядом с цифрой растёт ⚠ — таблица превышает порог опасного роста и требует внимания.

Автоочистка БД

Настройки автоочистки и аудита

Параметры retention (период хранения, после которого записи удаляются):

ПараметрSystemSettings ключDefaultЧто удаляется
СообщенияDB_CLEANUP_MSG_DAYS30 днейMsgStack где send_date < now − N
АудитDB_CLEANUP_AUDIT_DAYS365 днейAuditOperations где op_time < now − N
RADIUS-сессииDB_CLEANUP_SESSIONS_DAYS90 днейRadiusSessions где END_TIME IS NOT NULL AND END_TIME < now − N (только закрытые!)
NAS-событияDB_CLEANUP_EVENTS_DAYS30 днейNasStatusLog где event_time < now − N
Размер батчаDB_CLEANUP_BATCH_SIZE5000Сколько строк удалять за один DELETE (чтобы не лочить таблицу надолго)
ВключенаDB_CLEANUP_ENABLED1Тогл всей автоочистки

Расписание: Celery beat-задача db-cleanup-daily запускается ежедневно в 03:30 по серверному времени. Время фиксированное, для теста есть кнопка «Запустить чистку сейчас».

Что делает задача (billing/tasks/db_cleanup.py::run_db_cleanup):

  1. Создаёт запись в DbCleanupRun с status='running'.
  2. Для каждой из 4 таблиц считает порог cutoff = now - timedelta(days=N).
  3. В цикле: DELETE ... LIMIT batch_size пока есть записи. Между батчами 100мс паузы — снижает нагрузку.
  4. Записывает в результат: msg_deleted, audit_deleted, sessions_deleted, events_deleted, execution_time.
  5. Обновляет DbCleanupRun.status='ok' + finished_at. При ошибке — status='failed' + error.
  6. Если за прогон удалено > 100K записей — отправляет Telegram-уведомление.

Кнопки управления:

История запусков

Таблица последних 50 запусков из DbCleanupRun:

Аудит зомби-сессий на NAS

Аудит зомби-сессий

Зомби-сессия — это запись в RADIUS_SESSIONS с END_TIME IS NULL (то есть «сессия активна»), но при этом NAS не подтверждает её существование. Возникает когда:

Зомби-сессии искажают виджет «Онлайн сейчас» на дашборде (показывает большее число, чем реально), мешают корректному выбору IP из пула при переавторизации, и забивают `RADIUS_SESSIONS` лишними «открытыми» строками.

Beat-задача session-audit-hourly (billing/tasks/session_audit.py):

  1. Запускается раз в час (default; настраивается через SESSION_AUDIT_INTERVAL_MINUTES).
  2. Берёт ровно 4 NAS (default SESSION_AUDIT_NAS_PER_RUN=4) — те, у которых давно не было аудита (sort by last_audit_at ASC). Так за сутки покрывается ~96 NAS — больше, чем у любого реального оператора.
  3. Для каждого NAS берёт батч из 50 активных сессий (default SESSION_AUDIT_BATCH_SIZE=50). Это ~2 минуты времени NAS — не нагружает его.
  4. Для каждой сессии: radclient запрос Status-Server или Accounting Interim-Update к NAS — если NAS не подтверждает наличие session-id → метим как «подозрительную».
  5. Через 5 минут идёт повторная проверка тех же сессий. Если NAS ещё раз не подтверждает — сессия считается зомби, ставится END_TIME=now(), END_REASON='zombie-cleanup'.
  6. Counter подтверждённых zombie добавляется в SessionAuditRun.zombie_killed.
  7. Если zombie_killed > 0 — отправляется Telegram-алерт с разбивкой по NAS.

Параметры аудита:

ПараметрSystemSettings ключDefault
Интервал между запускамиSESSION_AUDIT_INTERVAL_MINUTES60 минут
NAS за один запускSESSION_AUDIT_NAS_PER_RUN4
Сессий за один батчSESSION_AUDIT_BATCH_SIZE50
Задержка повторной проверкиSESSION_AUDIT_RECHECK_DELAY300 сек (5 мин)
Telegram-алертSESSION_AUDIT_TG_ALERT1
ВключёнSESSION_AUDIT_ENABLED1

История аудитов ниже — таблица из SessionAuditRun: время, NAS, проверено сессий, найдено зомби, длительность, статус.

Выбор NAS для аудита

Выбор NAS для аудита

Таблица всех NAS с enabled=True и колонками:

На рабочий сервер сейчас в активном аудите 4 NAS: TOY, JOY, FAG, POT. Зомби-сессии редки (1–3 в неделю) — после фиксов (zombie-LOGGED + bigint миграция) это в основном NAS-перезагрузки.

Решение проблем

Чистка не отрабатывает в 03:30
Проверь Celery beat: docker logs app-celery-beat-1 | grep db-cleanup. Должна быть запись «Scheduler: Sending due task db-cleanup-daily». Если нет — beat не запущен или не видит задачу (импорт config/celery.py).
«MSG_STACK слишком большой» — 5М+ записей
Уменьши DB_CLEANUP_MSG_DAYS до 14 или 7 → жми «Запустить чистку сейчас». При первом проходе может удалить миллионы строк (займёт 10–20 минут). Дальше beat-задача держит размер в норме.
RadiusSessions растёт быстрее, чем чистится
В норме: 1 сессия = 1 строка с END_TIME IS NOT NULL через 5–10 минут. Если строк с END_TIME IS NULL много — есть зомби, запусти аудит. Если все закрытые, но всё равно много — увеличь DB_CLEANUP_BATCH_SIZE до 20000.
Telegram-алерт «zombie cleanup: 50+ sessions» каждый час
Один из NAS постоянно теряет Accounting-Stop. Проверь логи NAS, прошивку, сетевые потери до FreeRADIUS. Временно отключи аудит для этого NAS (чекбокс «Включён в аудит» = False) пока разбираешься.
Вернуть удалённые данные
Backup до чистки берётся ежедневно в 03:00 (за 30 минут до db-cleanup) — в /var/backups/carbon/. Восстановление таблицы: pg_restore -t msg_stack <dump-file>.dump в отдельную БД, потом INSERT нужных строк.

6.13. Диагностика

Набор готовых команд для диагностики проблем с конкретным клиентом или NAS. Запускаются прямо из веб-интерфейса, результат показывается в модалке-терминале.

Примеры команд: пинг NAS, проверка живости клиента, тест авторизации radtest, проверка SSH-доступа к NAS.

6.14. Массовые действия

Подменю с тремя инструментами обслуживания, объединёнными по смыслу «одно действие сразу для большой группы записей».

Подраздел «Разблокировать»

Массовая разблокировка клиентов. Открывается, если, например, партнёр-сборщик заплатил оптом за весь дом, и нужно одной кнопкой снять блокировку «долг» с десятков клиентов. Можно задать тип блокировки (b_negbal — долг, b_admin — админская) и фильтр клиентов (папка, тариф, дата блокировки).

Массовая разблокировка

Подраздел «Чистка БД»

Удаление тестовых/демо-данных при первичной настройке системы. Удаляет демо-клиентов, тестовые услуги, фейковые финансовые операции — всё, что было в стартовой поставке для демонстрации. Не трогает реальные данные оператора.

Массовая чистка БД

Запускать только один раз — после первичной настройки и до загрузки реальных клиентов. Действие необратимо, перед запуском система запрашивает подтверждение.

Подраздел «Закрытие периода»

Финальное действие месяца. Фиксирует балансы клиентов, переводит «корзину» (soft-deleted клиентов) в архив, готовит данные для бухгалтерской выгрузки. Запускается оператором вручную — обычно в первые дни нового месяца.

Закрытие периода

6.15. Производительность и оптимизация

В мае 2026 в биллинг внедрён пакет оптимизаций производительности. Главная цель — устранить «тяжёлые» страницы которые открывались по 3-4 секунды и тормозили работу операторов при одновременной нагрузке.

Что было сделано

Семь пакетов улучшений, выкаченных за один день без даунтайма биллинга:

  1. 8 composite-индексов PostgreSQL на самых частых полях запросов (audit, abonents_block, users_usluga, users.nas_id, radius.logged, finance_operations.op_date). Применены с CREATE INDEX CONCURRENTLY — без блокировки таблиц.
  2. Redis-кеши для главного дашборда (TTL 60с), мониторинга NAS (TTL 60с), карты клиентов (TTL 5 мин), VpnConst-настроек (TTL 60с с автоматической инвалидацией при сохранении через UI).
  3. Денормализация Abonents.last_payment_at — дата последнего платежа теперь хранится прямо у клиента, обновляется автоматически PostgreSQL-триггером на finance_operations.INSERT. Используется в фильтрах должников (period chips: новые/средние/старые/хронические).
  4. Денормализация Abonents.primary_nas_id + primary_pool_id — основной NAS и пул IP-адресов клиента (по самой свежей учётной записи Users). Используется в списке клиентов папки, глобальном поиске и AJAX-подгрузке. Обновляется тремя триггерами на users.INSERT/UPDATE/DELETE.
  5. statement_timeout=30s для пользовательских SQL-отчётов (/admin/reports/AdminCustomReports/) — защита от тяжёлых запросов которые вешали gunicorn-worker на минуты. После таймаута показывается понятное сообщение «Отчёт прерван по таймауту».
  6. Устранение N+1-запросов в 5 горячих местах: NAS list (DISTINCT ON для last_event), NAS Monitor (GROUP BY вместо N COUNT), AdminAccounts (3 count → 1 aggregate с filter=Q), folder/search (lookup из Nas/IpPull вместо JOIN Users).
  7. Убран функциональный cast op_date::date в FinanceOperations view — теперь используются B-Tree индексы вместо seq scan.

Что это дало

СтраницаБыло (cold cache)СталоУскорение
Должники, фильтр «не платили 90+ дней»3 821 мс0,34 мс11 000×
Папка клиентов545 мс100-300 мс2-5×
Глобальный поиск301 мс100-200 мс2-3×
Карточка клиента400 мс~100 мс
Главный Dashboard600-1500 мс<100 мс5-15×
Мониторинг NAS (auto-refresh)full SQL × 30сwarm cache3-5×

Главный эффект — страница должников (одна из самых частых для операторов отдела взысканий) теперь открывается мгновенно вместо 4 секунд ожидания.

Защитные механизмы

Документы (для разработчиков)

6.16. Captive Portal (финблокировка)

Captive Portal Engine — единый движок порталов для перехвата трафика через walled-garden. Должник с отрицательным балансом не отрезается полностью, а перенаправляется на страницу «пополните баланс», сохраняя доступ к ЛК, банкам и госуслугам — это ускоряет возврат дебиторки и снижает нагрузку на поддержку.

Обучающее видео 2:40

Как проверить готовность портала, чем опасен аварийный режим и почему нельзя привязывать магистральный NAS к гостевому профилю.

1920×1080 с озвучкой Скачать
flowchart TB
  CP["Captive Portal"] --> FIN["Финблокировка"]
  CP --> GST["Гостевые порталы"]
  CP --> PAY["Платежи"]
  CP --> ACL["Белые списки"]
  FIN --> RAD["RADIUS
walled-garden"] GST --> RAD ACL --> POOL["Пулы IP
и справочники"] PAY --> YK["Услуги
и ЮKassa"]

Раздел в админке: Настройки → Captive Portal (/admin/settings/captive_portal/). Четыре вкладки:

Готовность профиля и проверка конфигурации

Включённый профиль ещё не значит работающий портал. Резолвер RADIUS считает незаданными и пустое значение, и ноль: если не задан ни блок-пул, ни Filter-Id, должник получает отказ авторизации вместо страницы «пополните баланс». Вверху вкладки стоит список проверок, а если хоть одна не пройдена — красная плашка «Captive Portal не работает».

Вкладка «Финблокировка»: список готовности и результат проверки конфигурации

Проверяется пять вещей: профиль включён, задан блок-пул, осмысленный Filter-Id, у белого списка есть активные правила и режим жёсткого REJECT не перекрывает профиль. У каждой непройденной проверки — кнопка «Исправить», она подсвечивает нужное поле формы.

Кнопка «Проверить конфигурацию» идёт дальше и повторяет решение резолвера на живых данных: существует ли пул и остались ли в нём адреса, сколько активных правил в белом списке, совпадают ли резервные значения служебных настроек с профилем, отвечает ли страница-заглушка. Результат сохраняется с отметкой времени и автором — видно, когда портал проверяли последний раз.

flowchart LR
  A["Долг
b_negbal"] --> F{"Пул или
Filter-Id задан?"} F -->|"нет"| REJ["Отказ
авторизации"] F -->|"да"| WG["IP блок-пула
+ Filter-Id"] WG --> PAGE["«Пополните баланс»
+ белый список"] PAGE --> PAY["Оплата"] PAY --> OK["Снятие блока,
CoA, интернет"]
Белый список решает не меньше пула. Если у выбранной ACL-группы нет активных правил, должник в walled-garden не откроет ни личный кабинет, ни страницу оплаты — то есть не сможет заплатить и разблокироваться. Число активных правил видно и в списке готовности, и на вкладке «Белые списки».

Аварийный режим «Разрешить интернет всем»

Переключатель на вкладке «Финблокировка» отключает все блокировки на уровне RADIUS — на случай аварии биллинга, когда клиентов нельзя оставить без связи. Это самая опасная настройка раздела: пока она включена, должники пользуются интернетом бесплатно.

flowchart LR
  ON["Включение
с подтверждением"] --> LOG["Записаны автор,
время и срок"] LOG --> WORK["Блокировки
не применяются"] WORK --> OFF["Выключение:
по сроку или кнопкой"]

Как устроено

Движок построен на единой модели PortalProfile, рассчитанной на три типа портала: financial (финблокировка должников), guest (гостевой Wi-Fi турбаз — бесплатный и платный) и paid (платные точки):

Что делает оператор сам: создаёт блок-пул в Справочники → Пулы IP, выбирает его и остальные параметры на странице Captive Portal. Настройка walled-garden на боевом MikroTik (firewall + DNAT на заглушку) — однократная работа сетевого инженера.

Организации и права доступа

Раздел относится к группе прав «Настройки · Инфраструктура» — по умолчанию его открывает только администратор. Изменение настроек закрыто той же проверкой, что и сама страница: сотрудник без права не сможет поменять параметры даже прямым запросом.

Данные скоупятся по организации клиента-турбазы: список порталов, статистика платежей и выгрузка CSV показывают только то, что относится к организациям сотрудника. Портал чужой организации не открывается и по прямой ссылке — отвечает так же, как несуществующий. В списке гостевых NAS доступно только оборудование этой организации.

Привязка NAS — операция с последствиями. Гостевая ветка RADIUS срабатывает по одному признаку: NAS привязан к гостевому профилю. Если отметить магистральный NAS, все его клиенты при следующей авторизации уедут в гостевой walled-garden. Поэтому рядом с каждым NAS показано число учётных записей на нём, а при попытке привязать «занятый» — запрашивается подтверждение с перечислением.

Гостевой Wi-Fi портал для турбаз

На том же движке (PortalProfile тип guest) работает гостевой Wi-Fi портал — инструмент для турбаз, кафе, отелей: гость подключается к Wi-Fi, попадает на брендированную страницу, подтверждает телефон по SMS и заполняет анкету, после чего получает интернет. Турбаза собирает легальную базу контактов с согласием на рекламу и автоматически рассылает SMS-поздравления с днём рождения.

Вкладка «Гостевые порталы»: организация, состояние сети, готовность анкеты

В таблице разведены два состояния: «Сеть» — привязан ли NAS (или работает адаптер B), и «Анкета» — готовы ли форма и текст согласия. Портал может собирать анкеты и при этом не открывать интернет — это видно сразу. Чип «Сеть не привязана» со счётчиком отбирает такие порталы, поиск ищет по турбазе, названию портала и имени NAS.

Панель «Сеть портала»: пул, Filter-Id, ACL, гостевые NAS с числом учётных записей

Подключение: услуга «Гостевой Wi-Fi портал» (system_type=12) клиенту-турбазе (юр.лицо). При подключении автоматически создаётся портал с дефолтной анкетой (телефон + дата рождения). На карточке клиента появляется вкладка «Гость wifi» со статусом и ссылкой на портал.

152-ФЗ: портал не собирает данные, пока текст согласия на обработку ПДн не утверждён (флаг «текст утверждён» в ЛК турбазы). До утверждения портал в режиме настройки. Рассылки идут только гостям, отдельно согласившимся на рекламу.

Платный доступ к гостевой сети (продажа по QR)

Гостевой портал можно сделать платным: гость сканирует QR-наклейку, подтверждает телефон по SMS, выбирает тариф (например «100 ₽ — 3 часа»), оплачивает через ЮKassa и получает интернет на оплаченный срок. По истечении доступ автоматически отключается. Чек 54-ФЗ пробивает ЮKassa, выручка идёт на организацию-провайдера. Применение — платные точки в парках, на турбазах, на мероприятиях.

Поток гостя: сканирует QR → телефон + SMS-код → выбор тарифа → e-mail для чека → оплата картой → чек на почту → интернет на срок тарифа → по истечении снова на портал.

QR-наклейка. На вкладке «Гостевые порталы» кнопка открывает готовую брендированную A6-наклейку (PDF) прямо в окне — её печатают и размещают на ресепшене. Гость сканирует камерой и попадает на портал.

Вкладка «Платежи»

Вкладка отвечает на вопрос «принимаются ли деньги прямо сейчас», а не просто показывает список. Сегменты вверху — счётчики по статусам: Все · Оплачены · Ожидают · Ошибки · Истекли · Возвраты; клик по сегменту фильтрует таблицу. Рядом период (30 дней / 90 дней / год / всё время), выбор портала и поиск по e-mail, телефону или MAC.

Вкладка «Платежи»: сегменты по статусам, конверсия и доля ошибок

Показатели считаются по всей выборке периода, а не по видимой странице:

Контакты гостей скрыты. E-mail и телефон выводятся масками (p****@example.ru, 79177***96) и раскрываются по клику — персональные данные не «светятся» на экране, когда рядом кто-то есть. Экспорт CSV выгружает ровно то, что отобрано фильтрами, и только по доступным организациям.

Вкладка «Белые списки (ACL)»

Белый список — набор хостов, которые остаются доступны из walled-garden: личный кабинет, платёжные системы, банки, госуслуги. Правила раскрываются прямо в строке группы, рядом видно, в каком профиле группа используется, а группа без активных правил помечена красным.

Вкладка «Белые списки»: правила внутри группы и пометка «нет активных правил»

Строка правила показывает адрес, комментарий и состояние. Выключенное правило не работает, даже если группа выбрана в профиле — именно так портал и оказывается «настроенным, но бесполезным». Полное редактирование — в справочнике ACL, карандаш ведёт сразу к нужной группе.

Гости в СОРМ и срок хранения данных

Публичный Wi-Fi отличается от домашнего интернета не только техникой: постановления Правительства РФ №758 и №801 от 31.07.2014 требуют идентифицировать пользователя точки коллективного доступа и хранить сведения о нём и об оказанных услугах связи. Гость не клиент — договора с ним нет, — поэтому в выгрузку он попадает отдельными файлами, а не в таблицу ABONENT, согласованную с куратором.

flowchart LR
  G["Гость
подтвердил номер"] --> U["GUEST_USER
кто пользовался"] G --> I["GUEST_IDENT
журнал идентификаций"] S["Сессия RADIUS"] --> SS["GUEST_SESSION
оказанные услуги"] U --> EXP["Выгрузка СОРМ
по конфигу"] I --> EXP SS --> EXP
ФайлЧто внутриИсточник
GUEST_USER телефон и время его подтверждения, MAC, площадка и её владелец, первый и последний визит, число визитов, согласие на обработку данных, ФИО и e-mail — если анкета площадки их собирает гости порталов
GUEST_IDENT журнал подтверждений номера: когда запрошен код, когда подтверждён, IP и MAC клиента, IP точки доступа. Неудачные попытки тоже видны (RESULT = 0) OTP-сессии портала
GUEST_SESSION сведения об оказанных услугах: начало и конец сессии, длительность, объём трафика, IP и MAC сессии RADIUS по MAC гостя

Файлы добавляются кнопкой «Гостевой Wi-Fi» на странице отчётов конфига СОРМ (Оборудование → СОРМ → Отчёты). Отдельной кнопкой, а не в общем наборе: гостевые порталы есть не у каждого оператора, а состав файлов согласуется с куратором отдельно.

Периодичность — «вручную». Новые отчёты создаются без расписания: пока состав не согласован, файлы не должны уезжать на FTP регулятора сами. Периодичность включает оператор — как у остальных отчётов.
Сессии есть не всегда. GUEST_SESSION заполняется, когда площадка работает на нашем оборудовании (адаптер A). При адаптере B сессию держит MikroTik площадки и в биллинг она не попадает — факт доступа виден в GUEST_USER по времени окончания доступа.
Сколько хранить

Две нормы тянут в разные стороны, и обе обязательны: №801 требует хранить сведения не менее шести месяцев, а 152-ФЗ — не дольше, чем нужно для цели обработки. Поэтому срок задан настройкой GUEST_DATA_RETENTION_DAYS (по умолчанию 180 дней). Раз в сутки задача удаляет гостей старше срока вместе с анкетами, согласиями и журналом идентификаций; гостей с ещё действующим оплаченным доступом не трогает. Значение 0 отключает удаление — на случай, когда есть основание хранить дольше (запрос уполномоченных органов, спор по оплате).

Срок меньше 180 дней — нарушение №801. Задать его технически можно, но в журнале появится предупреждение: сведения о пользователях публичного Wi-Fi должны быть доступны уполномоченным органам полгода.

С телефона

Раздел рассчитан на работу с телефона: таблицы превращаются в карточки с подписями, фильтры сворачиваются в строку «поиск + Фильтры» с нижним листом, кнопки действий подписаны и помещаются в строку, а панель настроек сети открывается боковой панелью.

Гостевые порталы на телефоне Платежи на телефоне
Для запуска платного режима нужно: включить выключатель «Платный доступ через ЮKassa» в настройках, ввести REST-ключ ЮKassa (формат live_…) в Настройки → Платёжные системы и добавить домены ЮKassa (yoomoney.ru, api.yookassa.ru) в белый список гостевого NAS — иначе неоплаченный гость не сможет открыть страницу оплаты из walled-garden.