Граф Wiki

Биллинг. Основные настройки биллинга

Раздел описывает все семь пунктов главного меню админки СмИТ Биллинг — от тарификации и оборудования до справочников и работы с абонентами. Структура страницы повторяет структуру меню.

Биллинг — основные настройки
Содержание страницы

1. Дашборд

Главная страница админки по адресу /admin/welcome/. Перестроена в build 539–558 после /critique-аудита: иерархия KPI с hero-метрикой, единый визуальный язык компактных карточек (.dash-tile), все блоки скрываются автоматически при отсутствии данных.

Дашборд: KPI + IP-пулы + EBS Синхронизация

Содержание раздела

Шапка: 6 KPI-карточек

Верхний ряд — финансовый/операционный обзор. Hero-карточка «Должников» в 2× ширину с явным CTA «Все должники →» (build 539, единственный btn-primary на странице — даёт глазу первую точку фиксации). Остальные 5 KPI равноценные:

Все 6 градиентов унифицированы на linear-gradient(135deg, A, B) в одной палитре (раньше было 4 разных рецепта).

IP-пулы + EBS Синхронизация

Две компактные карточки в одну строку (build 540, ~110px высоты вместо 200+):

Унифицированные бейджи .dash-pill с цветовым кодированием (is-ok/is-warn/is-danger/is-info). JS-обновление каждые 60 сек через /admin/dictionary/ip_pull_stats/ и /admin/ebs_sync_status/.

Свежие проблемы (alerts feed)

Build 550. Отображается только при наличии алертов. Источники:

Каждая строка кликабельна → ведёт в соответствующий раздел. Левый красный бордер 3px, максимум 6 алертов. Видна всем, не только cash-группе.

Касса месяца

Build 543–548. Расчёт по календарному месяцу (с 1-го числа) из FinanceOperations:

Касса месяца + Платежи сегодня vs вчера

Сверху справа бейдж «✓ Сбор N%» (зелёный/синий/оранжевый по тем же порогам). Видна только для группы cash.

Платежи сегодня vs вчера

Build 549. 4 ячейки + sparkline по часам:

В шапке — Δ-pill «к вчера» (≥+5% зелёный, ≤−5% оранжевый, иначе ≈ синий).

Горячие абоненты + CRM сегодня

Build 551–554. Две карточки в одной строке: «Горячие абоненты» (col-lg-8) + «CRM сегодня» (col-lg-4).

Горячие абоненты + CRM с 3 последними тикетами

Горячие абоненты 24ч — скоринг и интерпретация

Топ-5 абонентов, кому стоит уделить внимание прямо сейчас. Алгоритм в billing/views/auth.py::welcome.

Скоринг (суммируется):

В блок попадают абоненты со score ≥ 15, отсортированные по убыванию. Цвет строки: ≥50 — красный, ≥25 — оранжевый, <25 — синий.

Что значат коды разрыва (END_REASON в RADIUS_SESSIONS):

Корневые причины (паттерны):

  1. Массовые Lost-Carrier каждые 4–10 минут (100+ разрывов за сутки) — обрыв физики: гнилой коннектор RJ-45, плохая обжимка, попадание воды в муфту, проблема PoE-инжектора, MAC-конфликт на порту коммутатора. Это не баг биллинга и не пароль (тот дал бы Access-Reject). Действие: выезд монтажника на точку.
  2. Десятки User-Request с короткими сессиями (~2 минуты) — клиентский роутер сам разрывает PPP. Возможно: двойной PPPoE-клиент в прошивке, жёсткий idle-timeout в настройках, скрипт автоматического переподключения. Действие: связаться с абонентом, проверить настройки роутера.
  3. Lost-Carrier раз в 30–60 минут — нестабильный канал, наводки, перегрев SFP/коммутатора. Менее острый сигнал, но повод заявить в техобслуживание.
  4. «Оплатил, но заблокирован» (50 pts) — смотрим в карточке последние FinanceOperations и AbonentsBlock. Обычно это сбой автоматики разблокировки после платежа. Жмём «Разблокировать» вручную, проверяем что в Celery-beat работает task recheck_blocks.

Что смотреть в карточке абонента:

Состав строки в блоке: цветной score-badge → имя абонента (ссылка на карточку) → список причин («N разрывов», «тикет», «оплатил, но заблокирован») → баланс (красный если долг) → стрелка перехода в карточку.

CRM сегодня — сводка из FreeScout (mailbox=1) за календарный день:

Под цифрами — 3 последних тикета (build 554) с цветной точкой статуса (active/pending/closed/spam), темой #N · subject (truncate 60 симв, кликабельно → support.smit34.ru/conversation/<id>), абонент + исполнитель + возраст. Кеш 5 мин по ключу dashboard_crm_24h.

Heat-map + Цели + Что нового

Build 555–557. Три узких карточки в одной строке.

Heat-map платежей 7×24 + KPI цели + Что нового

Heat-map платежей 7×24 (фиолетовая палитра) — день недели × час, цветовая интенсивность rgba(#6f42c1, alpha) от количества платежей. SQL:

SELECT EXTRACT(ISODOW FROM "OP_DATE"), EXTRACT(HOUR FROM "OP_DATE"), COUNT(*)
FROM finance_operations WHERE "OP_DATE" >= now() - 7 days AND "OP_SUMMA" > 0
GROUP BY 1, 2;

Hover увеличивает ячейку 1.4× с tooltip «N пл.». Помогает увидеть паттерн — например, пик платежей в Пн ~17:00.

KPI с целями — читает SystemSettings:

Без таргетов карточка скрыта. Цветной progress-bar (зелёный ≥100% / синий ≥70–80% / оранжевый ниже). Меняются через /admin/settings/system/?tab=params (категория dashboard) без релиза.

Что нового — 2 секции из dev_reports/ (build 557):

Каждая запись с нумерацией 1–4, кликабельна → /admin/reports/dev/?file=<path>. Подкаталог Документация исключён.

Итого за период + Финансовая динамика (старые блоки)

Сохранены: 3-row «Итого за период» (Поступления/Списания/Баланс с дельтами) и линейный график «Финансовая динамика» с переключателями типа (Область/Линия/Столбцы) и периода (7д/14д/30д/90д/180д/1г). Кнопки в чистом стиле (build 542 — фикс «серой губой» заливки .btn-outline-secondary).

Итого за период + Финансовая динамика — линейный график

Auto-refresh

Переключатель в шапке: Выкл / 30с / 1м / 2м / 5м / 10м. Состояние сохраняется в localStorage.dashboard_refresh_sec. Дефолт — выкл. Кнопка «Обновить сейчас» и таймер «Просмотрено: N сек/мин назад» рядом.

2. Тарификация

Раздел меню «Тарификация» содержит всё, что связано с услугами, которые получают абоненты, и правилами их тарификации.

Содержание раздела

Тарифы — назначение и роль

Содержание раздела

Тариф (Tarif) — это шаблон услуг и цен, которые получает абонент после подключения. Хранится в таблице tarif, редактируется на /admin/tarifs/Tarif/.

Зачем нужен тариф

Тариф решает три задачи:

  1. Группировка услуг — один тариф = пакет из 1-N услуг (интернет 100 Мбит + IPTV + статический IP) с заданными ценами;
  2. Автоматическое списание — Celery-задача billing_worker раз в день обходит активных абонентов, выбирает их тариф, проходит по списку услуг и списывает с лицевого счёта;
  3. Применение шейпера — через TarifRadiusParams FreeRADIUS получает атрибут Mikrotik-Rate-Limit или аналог. На NAS включается ограничение скорости.
Структура: Tarif + TarifUsersUsluga
СущностьЧто хранит
tarifИмя тарифа, флаги (IS_BUSINESS, IS_ARCHIVE), ссылки на лояльности, промо-настройки, резервный тариф
TARIF_USERS_USLUGA (TUU)M2M-связка тариф ↔ услуга. Один тариф содержит несколько TUU (по одной на каждую услугу пакета)
TARIF_RADIUS_PARAMSКастомные RADIUS-атрибуты для тарифа (Mikrotik-Rate-Limit, Pool-Name, Idle-Timeout)
TARIF_USERS_USLUGA_FOLLOW«Сопутствующие» услуги — авто-подключаются при смене тарифа (например, IPTV приставка прилипает к тарифу)
Как биллинг использует тариф
  1. Привязка к абоненту: Abonents.TARIF_ID (главное поле), опционально Users.TARIF_ID для специальных учёток;
  2. Ежемесячное списание (billing_worker.py):
    • Берёт abonent.tarif.tarif_tuu.all() — все услуги тарифа;
    • Для каждой услуги ищет соответствующую UsersUsluga у абонента (или создаёт новую);
    • Списывает каскадно: UsersUsluga.summ → usluga.summa → usluga.price (берётся первое заполненное);
    • Создаёт FINANCE_OPERATIONS с op_type=32 (тарифное списание);
  3. RADIUS: при авторизации FreeRADIUS подгружает TarifRadiusParams и отдаёт NAS-у соответствующий шейпер.
Архивирование vs удаление
Специальные сценарии
ПолеСценарий
PROMO_NEXT_ID + PROMO_DAYS или PROMO_MONTHЧерез N дней/месяцев тариф автоматически переключится на PROMO_NEXT (например, после льготного периода)
RESERVE_PLANРезервный тариф если основной недоступен. Используется в сценарии «отключение услуги, но сохранение базовой связи»
INACTIVE_TARIFНа какой тариф переводить абонента, если он не пользуется услугами (по дате ACT_DATE)
OWN_DISABLED_*Параметры добровольной блокировки: на сколько дней/месяцев минимум, какая стоимость в дни паузы
Не путайте Tarif и Usluga. Тариф — это пакет (Интернет 100 Мбит). Usluga — отдельная услуга (Интернет 100 Мбит, ОП, статический IP). Тариф состоит из услуг через TUU. У одной услуги может быть много тарифов, у одного тарифа — много услуг.

2.1. Тарифы

Список всех тарифных планов оператора. Открывается из меню Тарификация → Тарифы. Доступны вкладки «Активные» и «Архив»: архивные тарифы скрыты от выдачи новым абонентам, но по-прежнему действуют у тех, кто их уже подключил.

Список тарифов

Toolbar над таблицей:

Колонки таблицы:

Сортировка работает по любому столбцу — клик по заголовку. Активная сортировка выделяется синим цветом и стрелкой ▲/▼.

Bulk-операции — выберите тарифы галочками, появится плавающий тулбар со следующими действиями: В архив, Из архива, привязка к Линейке, CSV-экспорт выбранных, Удалить. Удаление блокируется на сервере, если у тарифа есть активные абоненты — выводится список ID, которые надо сначала перевести на другой тариф.

Модальное окно тарифа делится на четыре верхних вкладки (Основное / Услуги / RADIUS / Абоненты) и пять секций левой side-nav во вкладке «Основное». В шапке модалки бейдж «Назначено абонентам: N». На мобильном side-nav становится горизонтальным скроллбаром.

Вкладка «Основное» → секция «Общее»

Идентификация тарифа: название, описание, валюта, привязка к линейке услуг и оператору связи, флаг «Введён в эксплуатацию», «Архивный», «Отображать на сайте», период действия. Цена считается автоматически по сумме привязанных услуг.

Тариф — Основное

Секция «Лимиты»

Объёмы и пороги: лимит трафика (ГБ/мес), число подключений, число VPN-сессий, размер скидки за неиспользование. Используется в комплекте с услугами типа «Трафик».

Тариф — Лимиты

Секция «Смена тарифа»

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

Тариф — Смена тарифа

Секция «ОП и Доб. блок»

Параметры обещанного платежа (срок в днях, стоимость дня) и добровольной блокировки (минимум и максимум дней). Если поля пустые — функции отключены для этого тарифа.

Тариф — ОП и Доб. блок

Секция «NAT и прочее»

NAT-пулы (для трансляции серых IP в публичные), параметры RADIUS-атрибутов, дополнительные сетевые настройки. На большинстве тарифов остаётся пустой — заполняется только если используется специфическая сетевая логика.

Тариф — NAT и прочее

Вкладка «Услуги»

Список услуг, привязанных к этому тарифу. Сюда добавляются абонплата, бонусы по умолчанию, шейпер. Цена тарифа = сумма цен этих услуг (с учётом методов и периодичности).

Тариф — вкладка Услуги

Вкладка «RADIUS»

RADIUS-атрибуты, которые попадают в Access-Accept абонентов на этом тарифе: ограничение скорости (Mikrotik-Rate-Limit), ACL (Filter-Id), pool name и т.д. Эти значения уходят на NAS и определяют поведение сети для абонента.

Тариф — вкладка RADIUS

Вкладка «Абоненты»

Список абонентов, которые сейчас на этом тарифе. Можно перейти в карточку кликом по имени, либо массово перевести группу на другой тариф.

Тариф — вкладка Абоненты

Услуги (Usluga) — назначение и роль

Содержание раздела

Услуга (Usluga) — атомарная единица биллинга: то, что списывается со счёта (абонплата, статический IP, IPTV-пакет) или начисляется (бонус, компенсация). Хранится в таблице usluga.

Зачем нужна услуга

Услуга решает три задачи:

  1. Списание с баланса — раз в месяц billing_worker создаёт FinanceOperations на сумму услуги и уменьшает OSTATOK;
  2. Применение скорости через шейпер — поля RATE_IN, RATE_OUT попадают в RADIUS-ответ как Mikrotik-Rate-Limit;
  3. Бонусирование — услуги с отрицательной ценой или системные (system_type) выполняют разные роли: ОП, акции, компенсации.
TUU (шаблон) vs UsersUsluga (экземпляр)
СущностьЧто этоКто создаёт
TARIF_USERS_USLUGA (TUU)Шаблон: «эта услуга входит в этот тариф». Цена в TUU.summ может переопределять usluga.summaАдмин при настройке тарифа
USERS_USLUGA (UU)Экземпляр: «эта услуга подключена этому абоненту с такой даты». Хранит индивидуальные настройкиАвтоматически billing_worker при назначении тарифа, или вручную через карточку абонента

Один usluga может фигурировать в нескольких TUU (для разных тарифов) и иметь много UU (по одной на каждого подписчика). Удаление usluga заблокировано FK PROTECT, если есть хоть один TUU или UU.

Каскад цены

Когда billing_worker списывает абонплату, он берёт цену из первого заполненного источника (build 101):

  1. UsersUsluga.summ — индивидуальная скидка/цена для конкретного абонента;
  2. Usluga.summa — стандартная сумма услуги;
  3. Usluga.price — fallback цена (часто за единицу, например за Мбайт трафика).

В UI это видно в списке услуг: если используется fallback, цена подсвечена оранжевым с tooltip «эффективная цена: X ₽ / источник: Usluga.price».

Системные услуги

Несколько услуг имеют особое поведение, защищены от удаления и не показываются в обычных списках:

Usluga IDНазначение
30353«Обещанный платёж (5 дней, 30 руб/день)» — system_type=11. Управляется через ОП-flow в LK / Mobile / SMS
30280Системная служебная услуга — не удаляется
-3, -4Legacy-записи для старого формата ОП — оставлены для совместимости

Поле system=true у услуги — флаг «не показывать абоненту в счётах», даже если списывается.

Что делать со старыми услугами
Найти orphan-услуги (не привязанные ни к одному тарифу и ни к одному абоненту) можно через SQL:
SELECT u.* FROM usluga u
WHERE NOT EXISTS (SELECT 1 FROM "USERS_USLUGA" WHERE "USLUGA_ID"=u."ID" AND "DELETED"=false)
  AND NOT EXISTS (SELECT 1 FROM "TARIF_USERS_USLUGA" WHERE "USLUGA_ID"=u."ID")
  AND u."ID" NOT IN (30353, 30280) AND u."ID" >= 0;
Эти услуги — кандидаты на удаление при чистке БД.

2.2. Услуги и Бонусы

Услуги — то, что списывается с лицевого счёта (абонплата, доп. услуги, разовые работы). Бонусы — то, что начисляется на счёт (компенсации, скидки, акции). Для оператора это один справочник, разделённый по знаку суммы и типу.

Список услуг

Toolbar:

Колонки таблицы:

Системные услуги с фиксированными ID удалить нельзя — они используются ядром биллинга для обещанных платежей, бонусов и блокировок трафика.

Модальное окно услуги делится на 6 секций (вертикальная навигация слева). Открывается кликом по карандашу в строке списка.

Секция «Основные»

Название, тип услуги (трафик / IP-телевидение / стандартный / бонус / обещанный платёж / системный), цена, валюта, тип абонплаты (помесячно / разово / детализированно). Также — флаг «Включена», описание для оператора.

Услуга — Основные

Секция «Периодичность»

Когда услуга списывается. Варианты: ежемесячно (1-го числа или произвольная дата), разово (при подключении), в определённые дни месяца (1-го + 15-го), при превышении лимита. Дополнительные параметры: количество периодов, дата окончания, сдвиг даты списания.

Услуга — Периодичность

Секция «ЛК абонента»

Отображение услуги в личном кабинете абонента: видимое имя, иконка, описание, флаг «Доступна для подключения через ЛК» (абонент может сам её включить), флаг «Видна» (только показывать без возможности изменения).

Услуга — ЛК

Секция «Скидки»

Условия скидок при подключении услуги: фиксированная сумма / процент, условия применения (для определённых тарифов или типов абонентов), даты действия скидки.

Услуга — Скидки

Секция «Шейпер»

Параметры ограничения скорости (только для услуг типа «Трафик»). RX/TX скорости, burst-параметры, размер очереди. Эти значения попадают в RADIUS-атрибут Mikrotik-Rate-Limit или аналогичный для других вендоров.

Услуга — Шейпер

Секция «Связи»

Привязка к подсети (Tariff Ruleset — для специальной тарификации в локальной сети) и к типу услуги (используется в RADIUS-фильтре и СОРМ-выгрузке).

Услуга — Связи

2.3. Правила и сети

Подсети, которые тарифицируются по особым правилам — например, локальный ресурс с собственной ценой за ГБ, отдельный шейпер для торрентов, белая сеть для корпоративных абонентов.

Правила и сети

Параметры записи:

Удалить запись нельзя, если она используется в услугах — счётчик «Услуг» в строке показывает количество ссылок.

2.4. Карты оплаты

Серии бумажных карт оплаты с PIN-кодами. Каждая серия — это пакет карт номинала N₽ с уникальными PIN. Абонент пополняет счёт, введя PIN в личном кабинете или через кассу.

Карты оплаты

Серия определяется: имя серии, номинал, количество карт, дата выпуска, дата окончания действия. PIN-коды генерируются автоматически и выгружаются в PDF/CSV для печати.

Программы лояльности — назначение и роль

Содержание раздела

Программа лояльности — механизм скидок, акций и спец-условий для группы абонентов. Реализуется двумя моделями: Loyaltys (сама программа = шаблон) и AbonentsLoyalty (привязка абонента к программе = экземпляр).

Зачем нужна лояльность

Программы решают четыре задачи:

  1. Скидки — социальные тарифы (пенсионеры, многодетные), корпоративные скидки, акции «приведи друга»;
  2. Спец-условия для группы — например, «жильцы дома №15 платят на 100 ₽ меньше за интернет» (распределённая по адресу программа);
  3. Бонусы за продление — «оплатил год вперёд → 10% скидка»;
  4. Учёт в финансовых отчётах — отдельная строка «Скидки по программам лояльности».
Модели
МодельЧто хранит
Loyaltys
(шаблон)
Список самих программ. Поля: name, comment, priority, operator (FK на Abonents-папку с операторами), usluga_range (FK на UslugaRangeTypes), discount_percent, bonus_amount, recurring (ежемесячно), enabled
AbonentsLoyalty
(экземпляр)
Привязки «абонент ↔ программа». Поля: abonent (FK CASCADE), loyalty (FK CASCADE), start_date, end_date (NULL = активна), processed_date, next_date (для beat-задач списания)
Приоритет и применение

Один абонент может попасть в несколько программ одновременно (например: пенсионер + жилец акционного дома). При начислении скидки выбирается одна:

Связь с линейками услуг

Поле Loyaltys.usluga_range (FK на UslugaRangeTypes) ограничивает применимость:

Что делать со старыми программами
Где видеть программы абонента: в карточке абонента → вкладка «Лояльность». Там список с цветными бейджами по приоритету, дата начала/окончания, остатки бонусов. В UI /admin/tarifs/Loyaltys/ — список всех программ с количеством активных привязок.

2.5. Программы лояльности

Программа лояльности — это скидка или специальные условия для определённой группы абонентов: социальный тариф, корпоративные клиенты, многоквартирный дом, пилотный район.

Программы лояльности

Параметры программы:

В модалке программы есть две вкладки:

Программа применяется автоматически при ежемесячном списании. Назначить программу абоненту вручную можно из карточки абонента, вкладка «Лояльность». Удаление программы заблокировано, если есть активные участники.

2.6. Линейки услуг

Линейка услуг (модель UslugaRangeTypes) — это группа тарифов и услуг одного оператора связи или одной целевой аудитории. Линейка решает две задачи: логически разграничить тарифные планы между юр.лицами-операторами на одной инсталляции биллинга и технически ограничить, какие тарифы попадают в выпадающие списки в личном кабинете абонента и в программах лояльности.

Линейки услуг

Примеры использования

Модальное окно «Добавить / Редактировать линейку»

Открывается кнопкой «Линейка» в правом верхнем углу списка или иконкой карандаша в строке таблицы (urtModal в шаблоне usluga_range_types_list.html). Поля:

Сохранение через POST /admin/tarifs/range_types_crud/ (или /<id>/ для редактирования). Удаление — DELETE /admin/tarifs/range_types_crud/<id>/; защищено: если на линейку ссылается хотя бы один тариф или программа лояльности, сервер вернёт ошибку с понятным текстом.

Колонки списка

В шапке списка — поиск (Ctrl+K) по названию или оператору и счётчик отфильтрованных линеек. На мобильном — горизонтальный скролл таблицы.

Связи и где используется

3. Оборудование

Раздел меню «Оборудование» объединяет всё сетевое и абонентское оборудование, через которое работает биллинг.

Содержание раздела

3.1. NAS

Список всех NAS (Network Access Server) — серверов доступа: BRAS, MikroTik, Cisco, Redback и т.п. NAS — это «сетевой шлюз», через который абонент выходит в интернет.

Список NAS

Карточки KPI над таблицей: Всего NAS / UP / DOWN / Абонентов онлайн. Кликом по карточке-ссылке можно перейти на дашборд состояния (см. 2.2).

Чипы-фильтры: Все · UP · DOWN · Без RADIUS · Без SSH · PROD · Выключенные. Состояние сохраняется в URL (?chip=down) и можно поделиться ссылкой.

Колонки таблицы:

Подсветка строк: DOWN — красноватый фон, выключенные — приглушённый цвет, без RADIUS/SSH — оранжевая полоса слева, PROD — зелёная.

Кнопка «Перезапустить RADIUS» появляется в шапке после правки настроек NAS. Биллинг помечает изменения флагом radius_dirty; кнопка пропадает, когда рестарт выполнен и FreeRADIUS перечитал клиентов.

Модалка «Онлайн абоненты»

Клик по числу в колонке «Онлайн» открывает список абонентов с активной RADIUS-сессией на этом NAS. Полезно для оперативной диагностики — узнать кто сейчас онлайн, какой IP получил, когда стартовала сессия.

Модалка онлайн-абонентов NAS

В модалке: поиск по логину/ФИО/договору/IP, ссылки-абоненты в карточку (открывают новую вкладку), лог IN, IP-адрес из RADIUS-сессии, время начала и последнего обновления. Кнопки «Обновить» и «Закрыть». Подгружается до 100 строк за раз со скроллом для подгрузки следующих.

Модалка редактирования NAS — 6 вкладок

Карандаш в строке открывает большую модалку с шестью вкладками для всех настроек NAS.

Вкладка «Основное» — общие параметры: название, IP, маска, тип NAS (Mikrotik/Cisco/Redback…), адрес физической установки, серийный номер, регистрационный №, код активации, флаги «Включён» и «В эксплуатации». В шапке модалки бейджи «Активных» / «Макс» / «Авт» для быстрой оценки нагрузки.

NAS — вкладка Основное

Вкладка «RADIUS» — параметры аутентификации: secret-ключ, порты Auth/Acct/CoA, IP для CoA Disconnect, флаги поддержки разных RADIUS-возможностей. Эти данные FreeRADIUS читает из БД и формирует clients.conf при старте.

NAS — вкладка RADIUS

Вкладка «Авторизация» — параметры подключения абонентов: типы аутентификации (PPPoE / IPoE / Hotspot / 802.1x), VPN, тегирование VLAN, поведение при отсутствии IP в пуле.

NAS — вкладка Авторизация

Вкладка «Управление» — SSH/Telnet-доступ для дистанционного управления: адрес, порт, протокол, логин и пароль, тип ОС (для подбора шаблонов команд). Используется при ручном обращении к NAS из админки и в OSS-задачах (см. вкладка «OSS»).

NAS — вкладка Управление

Вкладка «IP-пулы» — какие пулы IP-адресов доступны NAS для выдачи абонентам. Можно назначить отдельные пулы для разных ролей: основной (PPPoE), NAT, белые IP (corporate), Hotspot. Если пул исчерпан, биллинг ходит по цепочке next_pull.

NAS — вкладка IP-пулы

Вкладка «OSS» — Operations Support System: автоматизация управления. Активация и деактивация абонентов через шаблоны команд (включить/выключить порт, сменить VLAN, обновить ACL). Команды отправляются через SSH/Telnet, описанные на вкладке «Управление».

NAS — вкладка OSS

Внизу модалки — кнопка «Полная форма» (открывает развёрнутую страницу со всеми низкоуровневыми параметрами), «Удалить» (с подтверждением), «Отмена» и «Сохранить».

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

Подключение конкретных моделей (MikroTik, Cisco, Redback, Ericsson), генерация конфигов и шаблонов — в разделе Интеграция с оборудованием → Интернет (NAS/BRAS).

3.2. Состояние NAS

Дашборд мониторинга всех NAS в реальном времени. Открывается из меню Оборудование → Состояние NAS. Делится на две вкладки: «Текущее состояние» и «История событий».

Вкладка «Текущее состояние»

Сводка по всем NAS на текущий момент: статус UP/DOWN, длительность последнего простоя, абоненты онлайн, sparkline за 24 часа, SLA-показатели.

Состояние NAS — Текущее состояние

Возможности вкладки:

Вкладка «История событий»

Лента всех переходов UP↔DOWN за последние дни. Каждое событие — это запись в таблице NAS_STATUS_LOG: дата/время, NAS, тип события (DOWN или UP), длительность простоя (только для UP-событий), детальное описание.

Состояние NAS — История событий

Возможности вкладки:

На основе истории строится SQL-отчёт «NAS uptime за 30 дней» в библиотеке отчётов (см. 3.2) — таблица uptime% по каждому NAS с количеством падений и общим простоем в минутах.

Кэш авторизаций (Redis fast-path для FreeRADIUS)

Виджет на дашборде «Состояние NAS». Решает проблему массового падения NAS: когда тысячи абонентов одновременно переавторизуются, FreeRADIUS не успевает обрабатывать пакеты из-за серии SQL-запросов на каждый Access-Request.

Без кэша один authorize() делал 5 SQL-запросов: Users + abonent + nas + NasRadiusParams + TarifRadiusParams + VpnConst, суммарно 5–15 ms на пакет. При 1000 пакетов/сек FreeRADIUS не успевал, очередь max_requests=16384 заполнялась за 16 сек, NAS retry'ил пакеты, БД захлёбывалась.

С кэшем (build 524+): при штатной авторизации (не заблокирован, NAS назначен, IP в пуле) ответ строится из снапшота в Redis — 1 ms вместо 10 ms, SQL не выполняется. Эффективная capacity FreeRADIUS Python module растёт в 5–10 раз.

Архитектура
Fast-path vs Slow-path

Fast-path (новый, без БД) активируется только при штатных условиях: enabled=True, abonent_enabled=True, есть пароль и NAS, ip в пулах NAS. Иначе срабатывает slow-path с ORM — для блокированных абонентов (нужна логика soft-block), OPT82, auto-assign NAS/IP, cache miss. После успешного slow-path снапшот пишется в кэш для следующих пакетов.

Инвалидация

5 Django signals (post_save) сбрасывают соответствующие ключи:

МодельЧто сбрасывается
UsersСвой ключ
AbonentsВсе ключи учёток абонента
AbonentsBlockВсе ключи учёток абонента
NasRadiusParamsВсе ключи users этого NAS
TarifRadiusParamsВсе ключи users на этом тарифе

Дополнительно — TTL 5 минут гарантирует протухание даже без сигналов (защита от рассинхрона при ручном UPDATE через psql).

Метрики и UI

Виджет «Кэш авторизаций» на дашборде «Состояние NAS» показывает:

Endpoint: GET /admin/equipment/radius_cache_stats/ возвращает JSON с метриками. POST /admin/equipment/radius_cache_flush/ сбрасывает весь кэш (только superuser).

Реальная статистика testbill через 5 минут после деплоя build 524 при штатной нагрузке ~10–20 авторизаций/сек: hits=838, misses=92, hit ratio=90.1%. При hit ratio 90% среднее время authorize() снизилось с 10 ms до ~2 ms, эффективная пропускная способность выросла в ~5 раз без увеличения ресурсов.

3.3. IPTV

Тот же список NAS, но отфильтрованный по типу оборудования IPTV-серверы: middleware и stream-серверы провайдера (TVIP Media, LFStream «Смотрёшка»).

IPTV

Привязка пакетов IPTV к услугам биллинга настраивается отдельно — в разделе Настройки → IPTV-пакеты (см. 5.6).

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

Настройка TVIP Media, LFStream и маппинга пакетов — в разделе Интеграция с оборудованием → IPTV.

3.4. Коммутаторы

Список всех коммутаторов в сети оператора. Записи используются для:

Коммутаторы

В списке доступны поиск, модальное добавление/редактирование/удаление. Колонка «Тип» показывает модель коммутатора (бренд + серия) и скрывается на мобильном.

3.5. Типы коммутаторов

Шаблоны типов коммутаторов: бренд, модель, число портов, наличие Opt82. Используется при создании коммутатора чтобы не вводить параметры вручную.

Типы коммутаторов

3.6. Настройки СОРМ

Список конфигураций выгрузки данных в СОРМ-3 на FTP-сервер РКН. Каждая конфигурация определяет: имя оператора СОРМ, FTP-сервер, расписание выгрузки, набор отчётов.

Настройки СОРМ

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

Принцип работы СОРМ-3, список интегрированных решений, метаданные UserAttributes, pre-flight валидация и REST API готовности — в разделе Интеграция с СОРМ3.

Открыть документацию СОРМ

4. Отчёты

Раздел меню «Отчёты» объединяет аналитику для руководителя, библиотеку SQL-отчётов, аудит действий персонала и журналы.

Содержание раздела

Пользовательские SQL-отчёты — назначение и роль

Содержание раздела

AdminCustomReports — таблица сохранённых SQL-запросов, доступных оператору через UI. Каждая запись = один отчёт с именем, описанием и SQL-кодом. Хранится в таблице ADMIN_CUSTOM_REPORTS, редактируется на /admin/reports/AdminCustomReports/.

Зачем нужны кастомные отчёты

Биллинг — это система с ~150 таблицами и 600+ полей. Готовых отчётов в админке всегда не хватает: бухгалтер хочет один формат, директор — другой, техподдержка — третий. Решение:

  1. Гибкость без релизов — оператор-аналитик пишет SQL прямо через UI, без ожидания deployment'а;
  2. Параметризация — встроенный синтаксис :Имя|тип$ позволяет добавлять параметры (даты, абонент, сумма) без знания Python;
  3. Экспорт — результат отчёта сразу в Excel/CSV/DBF без программирования;
  4. Историческая часть — каждый запуск сохраняется в AdminCustomReportsHistory, можно вернуться к старому результату.
Поля и связанные таблицы
Поле ADMIN_CUSTOM_REPORTSНазначение
NAMEНазвание отчёта (видно в UI)
DESCRIPTIONОписание для оператора (что отчёт показывает)
REPORT_TEMPLATESQL-код запроса (text)
REPORT_TYPEFK на AdminCustomReportsType — категория
ALLOW_IN_CABINETВидим ли абоненту в ЛК (раздел «Мои отчёты»)
TECH_ADMIN / FIN_ADMIN / FULL_ADMIN / CARD_ADMIN / READ_ADMINМатрица прав по ролям оператора (1 = доступно)

Связанные таблицы:

Параметры в SQL — синтаксис

Внутри SQL можно использовать спец-плейсхолдеры в формате :Имя|тип$ или :Имя|тип[аргументы]$. Когда оператор запускает отчёт — UI генерирует форму с этими параметрами, подставляет значения и выполняет SQL.

ТипПример SQLUI
dateWHERE op_date >= :Начало|date$Date-picker
sumWHERE op_summa > :Минимум|sum$Поле для рублей с конвертацией ×10^10
select[Model]WHERE abonent_id = :Абонент|select[Abonents]$Select2-AJAX по модели
choices[1^Янв|2^Фев|3^Мар]WHERE month = :Месяц|choices[1^Январь|2^Февраль]$Dropdown с фиксированными значениями

Парсинг — regex в AdminCustomReports._get_sql_fields(), подстановка в _execute_sql() с защитой от SQL-injection.

Типы отчётов

Справочник ADMIN_CUSTOM_REPORTS_TYPE — 7 категорий с цветными бейджами:

Один отчёт может быть без типа (NULL) или привязан к одной категории. На странице есть фильтр-вкладки по типам.

CodeMirror + AI Builder

CodeMirror 5.65.16 (build 449) — встроенный SQL-редактор в модалке:

AI Builder (build 455): кнопка «✨ AI» в toolbar. Открывает чат с Claude API:

  1. Оператор описывает что нужно: «Топ-10 должников за месяц с балансом и телефоном»;
  2. Claude задаёт уточняющие вопросы (период, фильтры);
  3. Возвращает готовый SQL с правильными именами таблиц/полей и параметрами;
  4. Оператор проверяет, при необходимости правит, сохраняет.

Контекст БД для Claude — schema из information_schema.columns для 15 ключевых таблиц + критические правила биллинга (DB_MONEY_KOEF, MPTT-фильтры, регистр имён).

Отчёты в ЛК абонента

Если у отчёта ALLOW_IN_CABINET=true — он показывается абоненту в ЛК (раздел «Мои отчёты»). При запуске:

Защита удаления отчёта: report_executions и AdminCustomReportsHistory ссылаются на отчёт с FK NO ACTION. Если есть история запусков — удаление падает с ошибкой. Сначала очистите историю или используйте архивирование (поле archive=true).

4.1. Библиотека отчётов

URL: /admin/reports/AdminCustomReports/

Каталог пользовательских SQL-отчётов. Отчёт — это сохранённый SQL-запрос с настраиваемыми параметрами, сгруппированный по типам (Техсервис, Бухгалтерия, Руководство, Поддержка и т.д.), который оператор запускает кликом и получает таблицу с возможностью экспорта в Excel, CSV или DBF.

Библиотека отчётов — список с типами и поиском

Список отчётов

Запуск отчёта

URL: /admin/reports/AdminCustomReports/<id>/executesql/

Кнопка ▶ Запустить в строке списка ведёт на форму с параметрами отчёта. Параметры извлекаются из SQL-кода по синтаксису :Имя|тип$ и рендерятся в форму автоматически.

Форма параметров отчёта (executesql)

На скриншоте — форма отчёта «Действия сотрудника» (#136): три параметра — Дата с, Дата по, Сотрудник. По умолчанию период — последний месяц (07.04 → 07.05), сотрудник = «Все сотрудники».

Внизу формы — четыре кнопки действий:

Пример: запуск с фильтром по конкретному сотруднику

Выбор конкретного сотрудника из выпадающего списка choices:

Форма с заполненными параметрами

На скриншоте — выбран сотрудник uspeshnyy (Aleksandr Uspeshnyy), период 01.05.2026 → 07.05.2026. Список доступных значений для параметра :Sotrudnik|choices[...]$ формируется на лету: SQL-генератор выбирает уникальных owner из AuditOperations и подставляет их в опции.

Пример: результат выполнения

После клика «Выполнить» срабатывает редирект на /admin/reports/AdminCustomReports/<id>/history/<run_id>/ — каждое выполнение получает свой run_id (хранится 30 дней для повторного открытия результата без перезапуска SQL):

Результат выполнения отчёта — HTML-таблица

На скриншоте — результат отчёта «Действия сотрудника» за месяц: 604 строки, колонки #, Дата/время, Сотрудник, ФИО, Событие, IP-адрес, Подробности. Каждая строка — один аудит-event с детализацией («FIN_TYPES: Тип фин. операции #28 «Перевод средств…»», «ABONENTS: создан абонент…», «USERS_USLUGA: услуга подключена…»).

Возможности страницы результата:

Модалка редактирования отчёта

Открывается кликом по имени отчёта или по карандашу. Делится на две колонки:

Live-counter под редактором обновляется на каждом change-event: «N строк · M парам.», справа от него — список найденных параметров с типами.

Синтаксис параметров

Параметры записываются в SQL как :Имя|тип$ и автоматически становятся полями формы запуска:

ТипСинтаксисUI-элементПередаётся в SQL как
date :Дата с|date$ Календарь (jQuery datepicker) String '2026-05-07' (ISO)
sum :Сумма|sum$ Числовое поле (₽) Float (умножается на 10^10 при необходимости)
select[Model] :Абонент|select[Abonents]$ Select2 с AJAX-поиском Integer (pk выбранной записи)
choices[v^L|v^L] :Месяц|choices[1^Янв|2^Фев]$ Выпадающий список фикс. значений String (значение выбранной опции)
choices[[code^]Label] :Sotrudnik|choices[[uspeshnyy^]Aleksandr] Расширенный выпадающий с экранированными значениями String (code)

Пример SQL с параметрами:

SELECT a.id, a.name, a.contract_number,
       aa.ostatok / 10000000000.0 AS balance_rub
FROM abonents a
LEFT JOIN admin_accounts aa ON aa.id = a.account_id
WHERE a.is_folder = false
  AND a.deleted = false
  AND aa.ostatok < -:Долг|sum$ * 10000000000
  AND a.create_date BETWEEN :Дата с|date$ AND :Дата по|date$
ORDER BY aa.ostatok ASC
LIMIT :Лимит|sum$;

AI-помощник написания SQL

Кнопка «✨ AI» в шапке открывает чат с ИИ-ассистентом (Claude Haiku 4.5 через Anthropic API, build 455). Ассистент знает структуру 15 ключевых таблиц биллинга (abonents, admin_accounts, tarif, finance_operations, users_usluga, pay_log, users, homes, nas, connection_points и др.) и формат параметров, умеет писать SQL по словесному описанию задачи.

Типичный сценарий:

  1. Оператор пишет: «топ-10 должников за месяц с балансом и телефоном»
  2. AI задаёт уточняющие вопросы: какой период (последний месяц / произвольный), какой минимальный долг, нужен ли фильтр по тарифу
  3. Оператор отвечает свободным текстом
  4. AI возвращает готовый SQL с блоком JSON {"name": "...", "description": "...", "sql": "..."}
  5. Зелёный draft-banner с кнопкой «➡ Создать отчёт» передаёт черновик в основную модалку с предзаполненными name/description/sql
  6. Оператор проверяет SQL в CodeMirror, при необходимости правит, нажимает «Сохранить»

Каждый AI-запрос пишется в AuditOperations (table_name='ADMIN_CUSTOM_REPORTS', descr='AI-консультация: …'). Запросы идут через Telegram-прокси (api.anthropic.com заблокирован для RU).

Права и audit

4.2. Аудит

Журнал всех значимых действий в биллинге: создание, изменение, удаление абонентов, подключение услуг, платежи, блокировки, изменения настроек, действия СОРМ.

Аудит

Каждая запись содержит дату/время, оператора, тип события, абонента и описание. Для большинства событий есть drill-down: клик по записи открывает изменённый объект в нужной вкладке.

Возможности списка:

4.3. Журнал платежей

Сводный журнал финансовых операций по всем абонентам — приходы, списания, сторно, обещанные платежи. Это представление таблицы FinanceOperations с серверной пагинацией (поддерживает сотни тысяч записей).

Журнал платежей

Возможности:

4.4. Журнал сообщений

Единый сводный журнал всех сообщений (MsgStack) по всем абонентам — SMS, Email, Telegram, Push, VK, ЛК. И точечные отправки из карточки абонента, и массовые рассылки попадают в один список. Доступен по адресу /admin/reports/messages/ (пункт меню «Отчёты → Журнал сообщений», build 588+).

Журнал сообщений: KPI, фильтры, таблица с колонкой Отправитель

Что внутри

Массовая рассылка

Кнопка «Рассылка» в правом верхнем углу открывает модальное окно для отправки одинакового текста группе абонентов через выбранные каналы (SMS / Email / Telegram / Push). Hard-limit — 500 получателей на одну рассылку (защита от случайного спама).

5 способов выбора получателей:

  1. Категории — 8 готовых chip-фильтров: Должники, Сейчас онлайн, Заблокированные, Об. платёж, Юр. лица, Без email, Без телефона, Не платил 30 дн.
  2. Тарифы — multi-select из ~78 тарифов. Отправит всем абонентам на выбранных тарифах.
  3. Папки — древовидный picker с MPTT-обходом всех вложенных подпапок автоматически. Поиск по имени, expand/collapse, кнопка очистки выбора.
  4. NAS — multi-select NAS-серверов. Отправит всем абонентам с учётками на выбранных NAS (через Users.nas_id).
  5. Поиск — Select2 AJAX по имени/договору для точечного выбора.

Рассылка: категория Юр.лица, preview 270 получателей и счётчик SMS-сегментов

Live-preview снизу всегда показывает: фактическое количество получателей, первые 10 имён + «…и ещё N», статистику доступности каналов («SMS: 267 · Email: 13 · TG: 0 · Push: 1»). Кнопка «Отправить» дизаблится при count = 0 или count > 500 (бейдж становится красным «макс 500!»).

Счётчик символов и SMS-сегментов справа от лейбла «Текст» обновляется на каждый ввод. Кириллица (UCS-2) → 70 символов на сегмент, латиница (GSM 7-bit) → 160. Цвет меняется на оранжевый при ≥2 сегментов и красный при ≥3.

Дерево папок

Вкладка «Папки» отрисована как полноценное tree-list с иконками и счётчиками абонентов в каждой папке (включая подпапки). Поддерживается keyboard-навигация (Tab → Enter/Space), live-фильтр по имени с автоматическим раскрытием родителей, кнопки Раскрыть всё / Свернуть всё / Очистить выбор.

Tree папок: SMIT (5554), ртк-червленое (49), Дубовый (27, выбран)

Рассылка по NAS-серверам

Полезно когда нужно предупредить абонентов конкретного NAS (профилактические работы, замена оборудования, миграция). Каждый NAS показывается с IP, статусом (включён / выкл) и количеством абонентов.

Рассылка по NAS: 2 выбранных NAS, preview 408 абонентов

Аудит рассылок

Каждая отправка пишется в AuditOperations (категория «Сообщения», см. раздел 4.2 Аудит):

Поле MsgStack.owner_id хранит pk оператора, поэтому в журнале можно отфильтровать «все мои отправки» или «всё что отправлял Петров».

Адаптивность

На mobile (≤767px) скрываются колонки «Договор» и «Отправитель» (видны только при открытии модалки деталей). KPI перестраивается в 2×2 grid. Всё кликабельно с touch.

Журнал сообщений на mobile (375px): 2×2 KPI grid, упрощённая таблица

Endpoint'ы

URLНазначение
GET /admin/reports/messages/Главная страница
GET /admin/reports/messages_json/Server-side список (фильтры + pagination)
GET /admin/reports/messages_kpi/JSON для KPI-карточек
GET /admin/reports/messages/export/CSV экспорт текущей выборки
POST /admin/reports/messages/broadcast/Массовая отправка
GET /admin/reports/messages/recipients_preview/Preview получателей перед рассылкой
GET /admin/reports/messages/tariffs_list/Список тарифов (для multi-select)
GET /admin/reports/messages/folders_list/Дерево папок (для tree-picker)
GET /admin/reports/messages/nas_list/Список NAS-серверов
GET /admin/reports/messages/senders_list/Список операторов с рассылками (для select-фильтра)

4.5. Разработка и логи

Раздел для администратора-разработчика. Доступен только пользователям с правами root. Делится на две вкладки.

Отчёты — встроенный markdown-просмотрщик внутренних документов проекта (отчёты по разработке, планы, документация).

Отчёты разработки

Поддерживается сортировка по дате/имени или ручной drag-and-drop, inline-переименование, создание папок, загрузка новых .md-файлов через интерфейс.

Логи — просмотр серверных логов прямо из админки.

Серверные логи

Доступны каналы:

Показываются последние 500 строк, есть кнопка обновления. Файлы автоматически ротируются (10 МБ × 5 копий).

4.6. Должники

Содержание раздела

Отдельная страница /admin/debtors/ — список абонентов с отрицательным балансом или активной блокировкой b_negbal. Раздел рассчитан на ежедневную работу бухгалтерии и операторов поддержки: KPI-блок сверху помогает быстро оценить ситуацию по компании, а drawer-панель — разобраться с конкретным абонентом без ухода на полную карточку.

KPI и статистика по периодам

Над таблицей — четыре карточки-показателя: общее количество должников, суммарный долг (в рублях), средний долг и доля должников с активным обещанным платежом. Карточки реагируют на выбранный период: переключатель chips сверху позволяет видеть динамику за сегодня / неделю / месяц / квартал / год. Цифры пересчитываются прямо в браузере без перезагрузки — у каждой карточки есть мини-индикатор изменения относительно предыдущего периода.

Фильтры и чипы
Таблица и сортировка

Колонки: ФИО, договор, тариф, точка подключения, баланс, дней без оплаты, телефон, email, статус. По умолчанию сортировка — по убыванию суммы долга. Клик на заголовок любой колонки переключает порядок (▲▼). Цветовое кодирование: красные строки — заблокированные за неуплату, жёлтые — на грани блокировки, серые — с активным обещанным платежом. В каждой строке справа — кнопка ℹ Подробнее, которая открывает drawer.

Drawer-панель абонента

Клик на ℹ открывает боковую панель справа (overlay поверх таблицы) с краткой сводкой по абоненту — без перехода на полную карточку и потери контекста списка. Внутри панели:

Массовые действия и экспорт

4.7. Миграция с других биллингов

Раздел /admin/settings/migration/ — мастер импорта данных абонентов из других биллинговых систем. Пошаговый wizard с предпросмотром и dry-run.

Carbon Billing 4 (Smit Billing legacy)

/admin/settings/migration/cb4/ — специализированный импорт из старой версии Smit Billing на базе Carbon 4 (Firebird). Учитывает специфику:

Загрузка из CSV

/admin/settings/migration/csv_loading/ — общий импорт абонентов/услуг/платежей через CSV файлы. Поддерживается:

Авто-импорт CSV (схемы)

/admin/settings/autocsv/ — настройка повторяющихся импортов: оператор сохраняет «схему» (маппинг колонок + способ обработки дубликатов), и потом загрузка одной кнопкой берёт CSV и применяет схему без повторной настройки. Полезно для ежемесячных выгрузок из бухгалтерии или экспорта из биллинга мобильного оператора (массовое зачисление платежей).

FrameworkFormGroupFields — назначение и роль

Содержание раздела

FrameworkFormGroupFields (FFG) — таблица настроек UI для всех админских форм биллинга. Управляет тем, какие поля видны на форме редактирования, как они сгруппированы в вкладки и в каком порядке. По сути это «генеральный настройщик» админки без правки кода.

Что это и зачем

Биллинг — это унаследованный движок с десятками моделей, у каждой по 30-100 полей. Большая часть из них — технический шум для оператора (поля Carbon 4 / EBS legacy). Решения:

Это безопасный способ упростить UI без боязни сломать логику.

Ключевые поля
ПолеНазначение
TABLE_NAMEИмя модели (USERS, ABONENTS, USLUGA — UPPERCASE)
FIELD_NAMEИмя поля Django-модели (то же что в Model._meta.fields)
INTERFACE_IDFK на интерфейс (NULL = дефолт для всех ролей)
GROUP_IDНомер вкладки/группы в форме (1 = основные, 2 = доп., и т.д.)
SHOW_ON_FORM0 = скрыто, 1 = видно (главное поле управления)
VERBOSE_NAMEРусское имя поля для UI (заменяет автоматическое из модели)
ORDERПорядок в группе (для drag-and-drop)
Как работает
  1. При открытии формы редактирования модели (например /admin/Abonents/<id>/) view вызывает safe_globals.get_groups_from_model('ABONENTS');
  2. Функция читает все записи FFG где TABLE_NAME='ABONENTS' и INTERFACE_ID IS NULL;
  3. Записи группируются по GROUP_ID → каждая группа = одна вкладка формы;
  4. Внутри группы поля сортируются по ORDER, скрытые (SHOW_ON_FORM=0) исключаются;
  5. Применяются VERBOSE_NAME для русификации лейблов;
  6. Кеш Django сохраняет результат — после изменения FFG нужен cache.clear().
Опасные моменты
  1. INTERFACE_ID=NULL — это дефолт, не «отсутствие настройки». Если поле SHOW_ON_FORM=0 для INTERFACE_ID=NULL, оно скрыто во всех интерфейсах. Чтобы показать только в одном — нужны 2 записи: NULL с 0 и INTERFACE_ID=1 с 1;
  2. Дубликаты Python dict — баг был с 'Users' ключом, появлявшимся дважды в _EXCLUDE_FORMS. Второе значение тихо перезаписывало первое. Перед добавлением нового ключа всегда grep;
  3. UPPERCASE имя таблицы — критично для PostgreSQL. 'abonents' != 'ABONENTS'. Используйте Model._meta.db_table.upper() чтобы не ошибиться;
  4. FIELD_NAME — Django-имя, не SQL-имя. Поле create_date в FFG, не "CREATE_DATE".
Реальные примеры скрытия полей
BuildМодельСкрыто
build 101Users (учётные записи)17 полей: IPV6, MASK, PHONE, EXT_ID, NAS_IP_LOCK, OPT82, IS_TEMPLATE, SNAT_PULL, SNATIP, SERVER, ALWAYS_LOGGED, HOST_IP, HOST_PULL, CREATE_DATE, OPT82_PARAM, IS_ALLOW_SEARCH, GPON_MODEM_PORT, HW_SERIAL, EQUIPMENT, SERVICE_TYPE
build 273UslugaSERVICE_TYPE (87% услуг имели его NULL)
build 101Users группа 325 «дополнительных»: DHCP_MASK, DHCP_ROUTE_IP, ASK_PASSWORD_LK, DEACTIVATE_STRING, ACTIVATE_STRING
Где править: UI /admin/settings/interface_settings/ (FrameworkFormGroupFieldsForm в billing/forms/framework_forms.py). После сохранения обязательно очистите кеш — иначе изменения не подхватятся: docker exec app-web-1 python manage.py shell -c "from django.core.cache import cache; cache.clear()"

4.8. Настройки интерфейса (формы)

/admin/settings/interface_settings/ — конфигурация полей форм для каждой модели. Через FrameworkFormGroupFields можно:

Пример использования: на форме Users скрыты 17 технических полей (IPV6, MASK, EXT_ID, IS_TEMPLATE, и т.д.) — оставлены только реально используемые. Изменения применяются для всех операторов одновременно после cache.clear().

4.9. CRM (legacy)

Старый раздел CRM (/admin/crm/CrmTask/) — планировщик задач сервисных инженеров с таймлайном, тегами, ответственными. Заменён на интеграцию с FreeScout (см. раздел 6 Интеграции) — все обращения от абонентов и сервисные задачи теперь в FreeScout. Старый раздел оставлен для просмотра исторических данных миграции с Carbon 4.

5. Игры

Раздел меню «Игры» — это пасхалка для администраторов: 8 классических игр, встроенных прямо в админку, и общая турнирная таблица.

Турнирная таблица игр

Список: Pacman, Tetris, Snake, Minesweeper, 2048, Breakout, Flappy Bird, Helicopter. Результаты сохраняются и попадают в Турнирную таблицу с лидерами по каждой игре.

6. Настройки

Раздел меню «Настройки» объединяет все параметры биллинга, интеграции, шаблоны печати, управление персоналом и обслуживание базы данных.

Содержание раздела

6.1. Настройки системы

Единая страница системных параметров с тремя вкладками: Параметры, Брендинг, Безопасность.

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

Настройки системы — параметры

Примеры параметров: лимит обещанного платежа в днях, частота auto-assign NAS, SLA-цели для NAS-мониторинга, частота автоматической очистки БД.

Вкладка «Брендинг» — фирменное оформление: название компании, логотип, цвет акцента, контактные данные. Эти данные показываются в админке, в личном кабинете и в шаблонах печати.

Настройки системы — брендинг

Вкладка «Безопасность» — политика финансовых операций (build 768)

На Безопасности — глобальные ограничения для ручных финансовых операций (создание/редактирование/удаление через UI). Доступна только пользователям группы root или is_superuser=True.

5 настроек (все по умолчанию выключены — стандартное поведение Django admin):

ToggleЧто меняет
Только суперпользователь может проводить ручные операции Скрывает кнопки «Приход»/«Расход», иконки редактирования и удаления для всех кроме is_superuser или членов группы root. Backend возвращает HTTP 403 при попытке POST. Просмотр истории операций остаётся доступен всем.
Разрешить выбор услуги при ручной операции «Расход» Показывает ссылку «+ Добавить услугу» в модалке Расхода. По умолчанию выключено — большинству провайдеров привязка не нужна, операция остаётся без FinanceOperations.usluga_id.
Максимальная сумма ручной операции (₽) Защита от опечаток («9000» вместо «900») и фрода. Если задана — backend отклоняет операции выше указанной суммы. 0 = без ограничения. Не действует на автоматические процессы (Celery worker, ЮKassa webhook, ОП).
Обязательное описание операции Запрещает пустое поле «Описание». Полезно для аудита — при разборе спорных операций ясно за что списали. Backend возвращает HTTP 400 если описание пусто.
Разрешить удаление финопераций Если выключено — иконка корзины скрыта в UI, backend возвращает 403 на DELETE-запросы. Для отмены неправильной операции остаётся только сторнирование (создание парной обратной операции с пометкой storno=True) — обе строки видны зачёркнутыми, что даёт полный аудит-trail для бухгалтерии.

Все ограничения проверяются и в backend (HTTP 403/400 на endpoints finops_ajax, finops_crud_ajax, FinOpChangeView) и в frontend (кнопки/ссылки/поля скрыты в HTML).

Важно: ограничения касаются только ручных операций через UI. Автоматические процессы — Celery worker billing_worker для абонплаты, webhook ЮKassa, обещанный платёж, auto-block по отрицательному балансу — работают как обычно, без проверок. Это нужно чтобы политика «только суперюзер» не сломала автоматическое начисление.

Шаблоны документов — договоры, акты, квитанции. Шаблон — это HTML или Word-файл с подстановочными переменными вида {{ contract_number }}, {{ abonent.name }}, {{ tariff }}. Готовый документ генерируется при печати из карточки абонента.

Шаблоны печати

6.3. Фискализация (АТОЛ)

Настройка интеграции с фискальным регистратором (онлайн-кассой) АТОЛ для соблюдения ФЗ-54. После успешного платежа автоматически отправляется чек в ОФД.

Фискализация АТОЛ

Поля настройки: адрес кассового сервера, идентификатор кассы, СНО (система налогообложения), ставка НДС.

6.4. Платёжные системы

Настройка приёма онлайн-платежей. Поддерживается несколько провайдеров.

Платёжные системы

Справа на странице — карточка с описанием комиссии каждого провайдера и краткой инструкцией.

Подробное описание API — на отдельной странице

Endpoints, схемы платежей, REST API v3 / HTTP, Wallet One, Mobile API — в разделе API, секция Платёжные API.

Маппа платёжных систем → FinTypes (build 510)

При успешном платеже webhook (lk/services/payment.py::_credit_abonent) создаёт FinanceOperations с типом операции, выбираемым по pay_system. Маппа настраивается через справочник типов + ключи SystemSettings:

pay_systemSystemSettings ключFinTypes по умолчанию
YooKassaLK_PAYMENT_OPTYPE_YOOKASSA«ЮKassa» (приход +1)
W1LK_PAYMENT_OPTYPE_W1«Wallet One (W1)» (приход +1)
cardLK_PAYMENT_OPTYPE_CARDtype_id=40 «Платёж с карты»
generic / fallbackLK_PAYMENT_OPTYPE_GENERICtype_id=23 «Оплата через платежные системы»

Зачем это нужно: до build 510 все онлайн-платежи через ЮKassa и W1 одинаково записывались под type_id=23 «Оплата через платежные системы», что мешало бухгалтерии разделять источники. Теперь каждая платёжная система имеет свой FinTypes — отчёты «итого через ЮKassa за месяц» работают через простой фильтр по op_type_id без парсинга поля descr.

Как изменить:

  1. Создать новый тип в Справочники → Типы фин. операций (например, «СБП» или «T-Bank»).
  2. Установить SystemSettings.value для соответствующего ключа = type_id нового типа. Через /admin/settings/system/ или management-команду set_system_setting LK_PAYMENT_OPTYPE_YOOKASSA 51.
  3. Webhook автоматически подхватит новое значение при следующем платеже (без рестарта).

Старые операции (под type_id=23) не переразмечаются — они остаются в истории под прежним типом. Только платежи, начисленные после миграции 0115 (build 510), получают новые типы. Это безопасно: отчёты с интервалом «после 2026-05-08» получат правильную разбивку, ранние периоды останутся как есть.

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 группы. Тест соединения проверяет валидность токена.

FreeScout-интеграция — назначение и роль

Содержание раздела

FreeScout — это open-source движок системы поддержки (HelpDesk), внешнее PHP-приложение. Установлен на отдельном поддомене (на demo: support.smit34.ru). СмИТ Биллинг интегрирован с FreeScout двусторонне: абоненты и тикеты синхронизируются обоими направлениями.

Что такое FreeScout и зачем

FreeScout заменил старый раздел HDSK (HelpDesk) внутри биллинга в build 78 (2026-04-03). Причины:

  1. Полноценный хелпдеск — теги, фильтры, шаблоны ответов, статусы тикетов, ответственные операторы, SLA, отчёты;
  2. Email-интеграция — abonент пишет на support@, FreeScout автоматически создаёт тикет;
  3. Mobile UI для оператора — есть нативный мобильный клиент, удобно в полевых условиях;
  4. Расширяемость — поддержка PHP-модулей: ClaudeAI, CarbonBilling, VkIntegration, TelegramIntegration.
Что синхронизируется с биллингом
ЧтоНаправлениеКак
Абоненты ↔ FS CustomersБиллинг → FSПоле Abonents.FREESCOUT_ID (build 58) хранит ID customer в FS. Celery-задача sync_abonent_to_freescout ежедневно в 03:00 синхронизирует имя, email, phone, custom fields (billing_id, contract_number, tariff, balance)
ТикетыFS ↔ БиллингFS — главный source of truth. Биллинг отображает тикеты абонента во вкладке HelpDesk через REST API
HDSK миграцияСтарый HDSK → FSКоманда manage.py migrate_hdsk_to_freescout [--dry-run] (build 78) — однократный перенос с сохранением комментариев и истории
ДубликатыВнутри FSBuild 109: 281 группа дублей абонентов в FS объединена скриптом dedupe_freescout_customers
REST API для FS-плагина

В FreeScout установлен PHP-модуль CarbonBilling — он показывает оператору информацию об абоненте прямо в окне тикета: баланс, тариф, контактные данные, последние операции. Модуль читает из биллинга через REST API:

EndpointЧто возвращает
GET /rest_api/v2/abonent_by_email/?email=xxxПоиск абонента по email — нужно когда тикет приходит из почты
GET /rest_api/v2/abonent_summary/<id>/Краткая информация (баланс, тариф, ФИО, телефон, статус) для отображения в FS

Конфиг FreeScout: переменная окружения SMIT_BILLING_API=https://testbill.smit34.ru/rest_api/v2/ в FS-.env. После изменения — php artisan config:clear && php artisan cache:clear.

Webhook-события из FS

Когда в FreeScout что-то происходит, он шлёт webhook на биллинг:

СобытиеДействие в биллинге
conversation.agent_reply_createdPush-уведомление абоненту через FCM (если есть привязанное устройство) — «Получен ответ от поддержки»
conversation.assignedTelegram-алерт назначенному оператору — «Вам назначен тикет №N от Иван Иванов»
conversation.status_changed(closed)Запись в AUDIT_OPERATIONS с drill-down на FS-тикет (для отчётов)

Регистрация webhook'ов: в FS админке Settings → Webhooks → Add, URL биллинга: https://testbill.smit34.ru/api/freescout/webhook/.

Вкладка HelpDesk в карточке абонента

В /admin/Abonents/<id>/ есть вкладка HelpDesk (build 78):

Сервис: billing/services/freescout_billing.py — REST-клиент с retry-логикой и кешированием.

Claude AI как агент в FreeScout

В FreeScout установлен модуль ClaudeAI — он автоматически отвечает на тикеты:

  1. Абонент пишет в чат ЛК → FreeScout получает сообщение;
  2. ClaudeAI читает контекст: профиль абонента (через CarbonBilling API), историю тикетов, базу знаний;
  3. Если вопрос «технический и понятный» (узнать баланс, как пополнить, забыл пароль) — Claude отвечает сам;
  4. Если вопрос требует человека (жалоба, ошибка биллинга, специфичный кейс) — Claude отвечает «передаю оператору» и помечает тикет escalated;
  5. Оператор видит тикет в FS, продолжает диалог.

Настройка: в FreeScout /claudeai/settings — API-ключ Anthropic, прокси для api.anthropic.com, billing_url для контекста.

Custom Fields в FreeScout: для корректной работы CarbonBilling нужны 4 поля у Customer: billing_id, contract_number, tariff, balance. Создаются вручную в FS админке (Manage → Custom Fields) — однократно при настройке. Биллинг заполняет их автоматически при синхронизации.

Вкладка «FreeScout + Claude AI»

Система обработки заявок поддержки. FreeScout — внешний хелпдеск (php-приложение); биллинг общается с ним через REST API. Claude AI — модуль FreeScout, который автоматически отвечает на простые вопросы абонентов в чате личного кабинета, эскалируя сложные обращения оператору.

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

Параметры: URL FreeScout, API-ключ, mailbox по умолчанию для новых заявок. Claude AI настраивается отдельно (API-ключ Anthropic, прокси для api.anthropic.com).

Вкладка «AIDA»

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

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

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

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

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

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

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

Параметры обоих сервисов: URL API, login/pass или API-ключ, ID оператора, тест соединения. Маппинг услуг → пакетов задаётся отдельно в разделе 5.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.6. IPTV-пакеты

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

IPTV-пакеты

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

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

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

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

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

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

Фирменное оформление ЛК: логотип, цвет акцента, доменное имя (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-сервера для исходящей почты: хост, порт, авторизация (логин/пароль), тип защиты (SSL / TLS / STARTTLS), отправитель «От кого». Кнопка «Тест» отправляет письмо на указанный адрес и показывает ответ сервера.

Сообщения — Email

Вкладка «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 delivery-status (builds 514–518)

Только для 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":"https://testbill.smit34.ru/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 = код ошибки

Реальная статистика (с testbill 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 отображения статуса (build 518)

На карточке абонента → вкладка «Сообщения» (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 контейнеров.
Подтверждено вживую

Build 518 (2026-05-07) на testbill, абонент Письменный Павел (#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.

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

Поддерживаемые события: низкий баланс, обещанный платёж активирован, обещанный платёж истёк, услуга подключена, услуга отключена, тариф изменён, платёж зачислен, блокировка / разблокировка. Доступные переменные: {{ abonent.name }}, {{ contract_number }}, {{ balance }}, {{ tariff }}, {{ amount }}, {{ end_date }} и т.д.

Редактор шаблонов: Plain + HTML (build 595–596)

Модальное окно редактирования шаблона расширено: 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-шаблоны писем (build 597)

В 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) добавлено в build 595 (миграция 0120_adminmsg_html_body). Существующие шаблоны не затронуты — у них html_body = NULL, Email-канал продолжает работать через txt. Заполнить HTML можно постепенно по мере необходимости (приветствие новому абоненту → красивая HTML-вёрстка, авто-уведомление о низком балансе → plain).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6.10. Лицензия

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

Лицензия

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

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

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

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

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

Типы копий:

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

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

Восстановление — модалка с выбором: только БД / только файлы / полное восстановление. Для типа 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 по серверному времени. Время фиксированное, для теста есть кнопка «Запустить чистку сейчас».

Что делает задача (build 453, 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 (build 453, 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 и колонками:

На testbill сейчас в активном аудите 4 NAS: TOY, JOY, FAG, POT. Зомби-сессии редки (1–3 в неделю) — после фиксов build 445 (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 абонентов) в архив, готовит данные для бухгалтерской выгрузки. Запускается оператором вручную — обычно в первые дни нового месяца.

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

7. Справочники

Раздел меню «Справочники» — все нормативно-справочные данные системы: пулы IP, ACL, адреса, валюты, статусы и т.д.

Содержание раздела

7.1. Пулы IP и VLAN

Подменю с двумя справочниками: пулы IP-адресов для выдачи через RADIUS и пулы VLAN для разметки портов на коммутаторах.

Пулы IP — назначение и роль

Содержание раздела

IP-пул — это диапазон IP-адресов [start_ip…end_ip], из которого FreeRADIUS выдаёт абоненту Framed-IP-Address при PPPoE/IPoE-авторизации. Хранится в таблице ip_pull, редактируется на /admin/dictionary/IpPull/.

Зачем нужен IP-пул

Пул решает две задачи:

  1. Автоматическая выдача IP абоненту. При авторизации FreeRADIUS вызывает allocate_ip_from_pool() (см. billing/services/ip_allocation.py), который находит первый свободный IP в диапазоне пула, помечает его в Users.IP и возвращает в RADIUS-ответе как Framed-IP-Address. Без пула — пришлось бы вручную проставлять IP каждому абоненту в карточке.
  2. Сегментация сети. Разным NAS можно привязать разные пулы → разные подсети для разных регионов / типов услуг (PPPoE-абоненты vs Hotspot, белые IP vs NAT).
4 роли пула на NAS

Один и тот же NAS может ссылаться на 4 разных пула, каждый со своим назначением:

Поле NASТег в UIКогда выдаётся
PULL_ID🟢 mainОсновной — для обычной авторизации абонентов
NAT_PULL_ID🟠 natДля абонентов с серым IP (NAT за роутером провайдера)
WHITE_PULL_ID🔵 whiteДля абонентов с белым IP — отдельный платный сервис
HOTSPOT_PULL_ID🟣 hotspotДля гостевых Wi-Fi-сетей (бесплатный интернет с ограничениями)

Какой пул использует FreeRADIUS — определяется по статусу абонента (tarif, has_white_ip, hotspot-флаг). На странице /admin/dictionary/IpPull/ в колонке «Привязка к NAS» видно, какие роли выполняет пул на каждом NAS.

Chain-цепочки (NEXT_PULL_ID)

Поле ip_pull.NEXT_PULL_ID позволяет связать пулы в цепочку резервирования: когда основной пул исчерпан, аллокатор автоматически переходит к следующему. Пример (testbill, 2026-05):

ЦепочкаОбъёмСтатус
JOY-1 → JOY-2 → JOY-3510 + 510 + 510 = 1530 IP34.2% занято (chain Σ)
TOY-1 → TOY-2 → TOY-3510 + 510 + 510 = 1530 IP39.6% занято (chain Σ)

Защита от циклов: _expand_next_pull_chain() ограничивает глубину 10 шагами и держит set уже посещённых ID. UI отображает chain-цепочку бейджем 🔗 с tooltip-разбивкой JOY-1 (510/510) → JOY-2 (14/510) → JOY-3 (0/510).

Когда нужно создавать новый пул
Что делать со старыми пулами
  1. Тестовые пулы (имя содержит «test», «tmp», «proba») — удаляются через UI кнопкой 🗑, если у пула 0 пользователей и 0 NAS-привязок;
  2. Декомиссированные пулы — поставить enabled=false вместо удаления, если есть исторические Users с IP из этого пула. Это сохранит историческую целостность;
  3. Не трогать пулы, упомянутые в любом из 4 полей nas.*_PULL_ID или в NEXT_PULL_ID другого пула — это сразу разрушит chain-цепочку.
Важно: при ручной правке Users.IP через карточку абонента, биллинг автоматически проверяет уникальность IP во всех источниках (users.IP, users.SNATIP, RADIUS_SESSIONS.IP) — это защита от race condition при выдаче дубля. См. billing/services/ip_allocation.py::collect_used_ips().

Пулы IP-адресов

URL: /admin/dictionary/IpPull/

Пул IP-адресов — это диапазон от start_ip до end_ip, из которого FreeRADIUS выдаёт Framed-IP-Address при авторизации абонента. Один пул привязан к одному или нескольким NAS-устройствам через 4 разных поля (см. роли). Список загружается AJAX, статистика занятости считается раз в минуту с кешем.

Список IP-пулов

Возможности списка
Добавление пула

Кнопка «+ Новый IP-пул» в правом верхнем углу открывает модалку:

Модалка добавления IP-пула

Обязательные поля:

Опциональные поля:

Что происходит после сохранения:

  1. Запись в БД (ip_pull): новый pull_id генерируется автоинкрементом.
  2. Audit-запись в AuditOperations (table_name='IP_PULL', object_id=pull_id).
  3. Кеш статистики (ip_pool_stats_v3) инвалидируется — следующая загрузка списка пересчитывает использование.
  4. Пул автоматически становится доступным в Select-полях NAS (Pull / NAT-pull / White-pull / Hotspot-pull) и в RADIUS_USERAUTH при авторизации.
Редактирование пула

Клик по строке (или иконке карандаша справа) открывает ту же модалку с предзаполненными полями:

Модалка редактирования IP-пула

Все поля редактируются. Изменение start_ip / end_ip у используемого пула — опасная операция: если новый диапазон не покрывает уже выданные адреса, абоненты со «старыми» IP не попадут в пул при следующей авторизации (allocate_ip_from_pools не считает их занятыми, может выдать дубль). Прежде чем сужать диапазон — проверьте через find_ip_duplicates --pool_id=N.

Удаление через кнопку «Удалить» внутри модалки. Защита: пул нельзя удалить, если на него ссылается хоть один NAS (через любое из 4 полей pull_id / nat_pull_id / white_pull_id / hotspot_pull_id) или другой пул через next_pull_id. Сервер вернёт ошибку «Пул используется в N NAS / M пулах. Сначала уберите ссылки.»

Роли пула на NAS (4 поля)

Один NAS может ссылаться на пул через 4 разных поля, что определяет как IP из этого пула используется:

Поле NASРоль (бейдж)Назначение
pull_id🟢 mainОсновной пул для выдачи серых IP абонентам через DHCP/PPPoE.
nat_pull_id🟠 natNAT-пул: серые IP, скрытые за внешним NAT. Через RADIUS уходит как Framed-IP-Address, на NAS настраивается NAT в сторону интернета.
white_pull_id🔵 whiteБелые (публичные) IP. Выдаются за дополнительную плату, маршрутизируются напрямую без NAT.
hotspot_pull_id🟣 hotspotПул для гостевого Wi-Fi (hotspot). Часто короткие диапазоны с walled-garden ACL.

В списке пулов рядом с именем NAS показываются бейджи ролей для каждого NAS, использующего этот пул. Это помогает быстро понять «куда подключён пул» без перехода на страницу NAS.

Цепочки пулов (next_pull)

Когда абонентов больше, чем размер одного /24-пула (254 адреса), используется цепочка: основной пул ссылается на резервный через next_pull_id. Алгоритм allocate_ip_from_pools() в billing/services/ip_allocation.py:

  1. Проверяет основной пул (NAS.pull_id).
  2. Если он исчерпан — переходит в next_pull.
  3. Из next_pull снова в его next_pull, и т.д.
  4. Защита от циклов: max_depth=10 + seen-set по pull_id.
  5. Если все пулы цепочки исчерпаны — fallback на глобальный FALLBACK_IP_POOL_ID (если задан в VpnConst), затем reclaim из offline-абонентов, затем Telegram-алерт.

Пример (testbill, 2026-05): JOY-1 → JOY-2 → JOY-3, каждый по 510 адресов. Σ-блок в UI показывает «🔗 Σ 1006 / 1530 ×3» — итого занято 1006 из 1530, цепочка из 3 пулов.

Visual: на пуле, у которого задан next_pull_id, в колонке «Название» появляется бейдж «🔗 → #N». Клик открывает модалку следующего пула.

Пулы VLAN — назначение и роль

Содержание раздела

Пул VLAN — это диапазон номеров VLAN [start_vlan…end_vlan], закреплённых за конкретным оператором связи или сегментом сети. Хранится в vlan_pull, редактируется на /admin/dictionary/VlanPull/.

Зачем нужен пул VLAN

Пул VLAN решает две задачи:

  1. Сегментация L2-сети. Каждой точке подключения (CONNECTION_POINTS) можно присвоить отдельный VLAN из пула — изолировать трафик абонентов друг от друга на коммутаторах. Биллинг подсказывает свободный VLAN из пула при создании точки.
  2. Учёт использования номеров VLAN. На корпоративных сетях (или нескольких операторов в одной инфраструктуре) выделение VLAN строгое: например, оператор А получил 100–200, оператор Б — 201–300. Пул не даёт случайно занять чужой номер.
Где это поле используется
ГдеПоле / связьЧто происходит
vlan_pullstart_vlan, end_vlanДиапазон номеров (1–4094 в стандарте 802.1Q)
vlan_pulloperator (FK на Abonents)За каким провайдером закреплён пул (если несколько ISP в одной инфре)
CONNECTION_POINTSVLAN-номер (через UI выбора)Присвоенный точке VLAN. Из пула выбирается свободный номер при создании точки
Когда нужно создавать пул VLAN
Что делать со старыми пулами
Single-operator hide-fix (build 456): если в системе только один оператор связи, в модалке создания пула колонка «Оператор» автоматически скрывается через CSS-класс .vp-op-col-hidden (бьёт Bootstrap-сетку с !important). Это упрощает UI для маленьких ISP.

Пулы VLAN

URL: /admin/dictionary/VlanPull/

Пул VLAN — диапазон номеров VLAN, выделенных оператору связи. Используется при настройке точек подключения (одна точка = одна VLAN из пула) и в шаблонах конфигурации коммутаторов.

Список VLAN-пулов

Возможности списка
Добавление и редактирование

Кнопка «+ Новый VLAN-пул» открывает модалку:

Модалка добавления VLAN-пула

Поля:

Модалка редактирования VLAN-пула

На скриншоте edit-режим: модалка идентична add, но с заполненными значениями + кнопкой «Удалить» снизу слева.

Защита удаления (build 456): VLAN-пул нельзя удалить, если он использован хотя бы в одной точке подключения. Сервер вернёт ошибку «Пул используется в N точках. Удаление запрещено для сохранения связей.»

Мониторинг и алерты

Beat-задача ip-pool-monitor (раз в час) обходит все enabled IP-пулы:

На дашборде /admin/welcome/ — компактный виджет «IP-пулы» с топ-3 опасными и сводкой использования.

7.2. Контроль доступа (ACL)

Подменю с двумя справочниками для управления сетевыми правилами доступа.

Подраздел «ACL группы»

Именованные списки правил для firewall: «Домашние пользователи», «Корпоративные», «VIP», «Социальный тариф» и т.п. Каждой группе соответствует набор правил — разрешения и запреты по портам, протоколам и адресам. Группа назначается абоненту в его карточке (на вкладке «Точки подключения») и попадает в RADIUS-ответ как Filter-Id.

ACL группы

Подраздел «Списки разрешённых сайтов»

Белые списки доменов для абонентов в финансовой блокировке (captive portal): сайт оператора, страница оплаты, личный кабинет. Когда абонент в долге, NAS ограничивает его трафик до этих доменов — чтобы он мог пополнить счёт, но не мог пользоваться интернетом для развлечений.

ACL — белые списки

7.3. Все адреса

Подменю с тремя справочниками для адресной системы оператора.

Адреса (Homes) — назначение и роль

Содержание раздела

Адрес (Homes) — запись в иерархическом справочнике Россия → регион → город → район → улица → дом. Хранится в таблице homes как самоссылочное дерево (через parent_id), редактируется на /admin/dictionary/Homes/.

Зачем нужен справочник адресов

Справочник homes решает четыре задачи:

  1. Привязка абонента к физическому адресу. Поле Abonents.HOME_ID → конкретный дом. Используется для печатных форм договоров, счетов, СОРМ-выгрузок (поле ABONENT_ADDR).
  2. Привязка оборудования к месту установки. Поле switch.HOME_ID и nas.HOME_ID — где установлена железка. Это техдокументация для выезжающих инженеров.
  3. Точки подключения. CONNECTION_POINTS.HOME_ID — на каком доме точка подключения находится. Один дом может иметь много точек (квартиры, офисы).
  4. Геокодинг и карта. Поля lat/lon хранят координаты дома (геокодируются через DaData). Используются на карте абонентов /admin/abonents/map/ — кружки с цветом по статусу абонента.
Где используется адрес
ГдеПолеЧто происходит
AbonentsHOME_IDАдрес абонента — печатные формы, СОРМ, карта
usersHOME_IDАдрес учётной записи (если несколько услуг по одному абоненту в разных точках)
switchHOME_IDГде стоит коммутатор (для техника)
nasHOME_IDГде стоит NAS (обычно центральный узел)
CONNECTION_POINTSHOME_IDТочка подключения в подъезде/щитке этого дома
Дерево и геокодинг

Адреса организованы как самоссылочное дерево:

Подробнее про карту см. «Карта абонентов».

Что делать с адресами без жителей

В аудите БД часто видны адреса без привязанных абонентов и Users (на testbill 2026-05: 992 шт). Это не мусор, а адресная база региона — список потенциальных адресов для подключения.

Важно: при создании нового абонента UI подсказывает существующие адреса через Select2-AJAX (billing/static/js/address_widget.js). Если адреса нет в справочнике — лучше сначала добавить его, а уже потом привязать абонента. Это сохраняет адресное дерево чистым.

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

Иерархия Россия → регион → город → район → улица → дом. Хранится как дерево с unicode-сравнением, дома содержат геокоординаты (lat/lon), индекс, дату начала обслуживания.

Адреса

Возможности списка:

Подраздел «Точки подключения»

Физическое место подключения абонента: коммутатор + порт + VLAN. Один дом может содержать несколько точек (если в подъезде несколько коммутаторов или несколько провайдерских узлов). Используется для DHCP Option 82, RADIUS-фильтра и СОРМ-выгрузки.

Точки подключения

Подраздел «Типы адресных единиц»

Словарь сокращений: ул, пер, наб, пр, пл, бульв, проезд, тупик, шоссе, поселение, мкрн и т.д. Используется для нормализации и парсинга полных адресов, передачи в DaData и в форматирование адреса для печатных документов.

Типы адресных единиц

7.4. Реквизиты

Шаблоны атрибутов для физлиц и юрлиц. Физлицо: паспорт серия/номер, дата выдачи, кем выдан, адрес регистрации, СНИЛС. Юрлицо: ИНН, КПП, ОГРН, юридический/физический/почтовый адрес, расчётный счёт, банк, БИК, корреспондентский счёт, в лице, директор.

Реквизиты

Атрибуты автоматически создаются у абонента при создании в зависимости от типа (физлицо / юрлицо). Заполняются во вкладке «Реквизиты» карточки абонента. Для юрлиц также собираются на вкладке СОРМ.

7.5. Валюты

Список валют биллинга. По умолчанию — рубль (₽). Для мультивалютных схем можно добавить USD/EUR с курсами.

Валюты

7.6. Выходные дни

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

Выходные дни

Статусы абонентов — назначение и роль

Содержание раздела

Статус абонента — состояние его подключения: «Активный», «Заблокирован», «На паузе», «Тестовый период», «Должник», «Корпоративный VIP». Хранится через FK Abonents.status_id → таблицу STATUS. Управляется в /admin/dictionary/Status/.

Зачем нужны статусы

Статусы решают четыре задачи:

  1. Визуальная индикация — цветной бейдж рядом с именем абонента в списке. Оператор видит «должник», «новый», «активный» одним взглядом;
  2. Автоматизация смены — при определённых событиях (баланс ушёл в минус, истёк тестовый период) Целери-задача меняет статус автоматически;
  3. Триггер для сообщений — при смене статуса автоматически шлётся email/SMS/push/Telegram-сообщение из StatusMsgTemplate;
  4. Изменение поведения сети — каждому статусу можно привязать StatusRadiusParam — RADIUS-атрибуты для NAS (например Filter-Id := block у статуса «Заблокирован»).
Поля и связанные модели
Поле STATUSНазначение
nameИмя статуса (видно оператору)
default_statusАвтоматически присваивается новым абонентам
set_on_activateУстанавливается при активации подключения
block_on_setСразу блокировать абонента (создаётся AbonentsBlock)
move_toFK на папку (Abonents с is_folder=true) — куда перемещать абонента при смене статуса
move_to_group_from_tarifПапка берётся из тарифа (для агентских схем)

Дополнительные модели:

4 категории событий аудита

В AUDIT_OPERATIONS поле op_type_category разделяет события на 4 группы (build 149+):

КатегорияЧто попадает
statusСмены статуса абонента (ручные и автоматические)
blockСоздание/снятие AbonentsBlock
paymentФиноперации, изменения баланса
SORMИзменения СОРМ-полей абонента (паспорт, ИНН, ОГРН)

В UI вкладки «Аудит» карточки абонента можно фильтровать по категории — получаешь только статусные смены или только финансовые операции.

Связь с шаблонами сообщений

Когда статус меняется, через post-save сигнал биллинг проходит по StatusMsgTemplate для нового статуса и отправляет:

Управление шаблонами — в модалке статуса (вкладка «Шаблоны»). Каждый канал включается / выключается отдельно.

Связь с RADIUS

При авторизации абонента FreeRADIUS читает его текущий статус и применяет связанные с ним атрибуты:

Это альтернатива полной блокировке — абонент остаётся подключён, но с ограничениями. Гибче чем boolean enabled=false.

Build 149+ добавил AJAX CRUD для статусов с модальными окнами на 2 вкладки (Шаблоны сообщений + RADIUS-параметры). До этого в Carbon 4 редактирование шаблонов было через отдельную страницу — теперь всё в одной модалке статуса.

7.7. Управление статусами

Подменю с двумя справочниками для управления пользовательскими статусами абонентов.

Подраздел «Статусы»

Пользовательские статусы абонента: «Тестовый период», «Должник», «Перерыв на лето», «Корпоративный», «VIP» и т.п. Каждый статус имеет цвет (показывается как бейдж рядом с именем абонента в списке) и шаблон сообщения, которое отправляется автоматически при назначении статуса.

Статусы

В модалке статуса есть вкладки для шаблонов сообщений по каналам (Email / SMS / Push / Telegram) и RADIUS-параметров (если статус должен влиять на скорость или ACL абонента в авторизации).

Подраздел «Настройка статусов»

Правила автоматической смены статуса по событиям: платёж выше N руб → статус «Активный»; задержка платежа > N дней → статус «Должник»; истечение договора → статус «Архив». Правила применяются Celery-задачей раз в час.

Настройка статусов

7.8. Типы отчётов

Категории SQL-отчётов для разделения по ролям сотрудников: Техсервис, Руководство, Бухгалтерия, ЦОК, Телефония, Техподдержка, ЦУС. Каждый тип имеет цвет и иконку.

Типы отчётов

Управляется из этого справочника или прямо из Библиотеки отчётов кнопкой «Типы».

7.9. Единицы измерения

Словарь единиц измерения для услуг: МБ, ГБ, минуты, штуки. Используется в названиях услуг и в детализации расхода.

Единицы измерения

7.10. Периоды

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

Периоды

8. Абоненты

Раздел меню «Абоненты» — основной рабочий раздел оператора.

Содержание раздела

Папки абонентов — назначение и роль

Содержание раздела

Папка абонентов — это «контейнер-узел» в дереве абонентов с флагом IS_FOLDER=true. Хранится в той же таблице abonents (поле parent_id), но не имеет тарифа, счёта и финансовых атрибутов. Корневая папка имеет ID=9001.

Зачем нужны папки

Папки решают три задачи:

  1. Логическая группировка — абоненты раскладываются по агентам, филиалам, типам клиентов, географическим зонам. Это удобно для навигации и фильтрации в админке;
  2. Контроль доступа операторов — через AbonentGroupAccess Django-группа привязывается к конкретным папкам, и оператор видит только своих абонентов;
  3. Корзина удалённых — обычно это спец-папка (на testbill #9003 «Удалённые»), куда перемещаются deleted=true-абоненты для возможного восстановления.
MPTT-дерево

Иерархия реализована через библиотеку django-mptt:

Группы и контроль доступа

Связь «Группа Django ↔ Папка абонента» хранится в AbonentGroupAccess:

Что делать со старыми папками
Важно про корзину: при Abonents.delete() в коде (build 117+) выполняется soft-delete: deleted=true + enabled=false, абонент остаётся на своём месте в дереве. Если хотите централизованную «Корзину» — настройте автоматическое перемещение в #9003 через bulk action или добавьте логику в delete() модели.

8.1. Абоненты

Главный список абонентов. Можно работать в двух режимах: дерево папок (по умолчанию) и глобальный поиск. Папки удобны для иерархической навигации по агентам/филиалам/адресам, глобальный поиск — для массовых операций над всей базой.

Папка абонентов

Папка абонентов (/admin/Abonents/<id>/, корень — /admin/Abonents/9001/) — список абонентов конкретной папки с поддержкой подпапок. Каждая папка имеет свой набор быстрых фильтров и массовых действий, рассчитанный на ежедневную работу оператора с одним сегментом базы.

Папка абонентов

Содержание раздела
KPI и статистика

Над таблицей выводятся 4–5 счётчиков по абонентам текущей папки (без учёта подпапок): Активных, Сейчас онлайн, Выключенных, Всего, Юр. лиц. Числа обновляются вместе с применённым фильтром-чипом.

Чипы быстрых фильтров

Под счётчиками — ряд чипов с готовыми фильтрами. Активен может быть только один. Повторный клик снимает фильтр. Состояние сохраняется в URL — ссылку можно отправить коллеге:

Колонки таблицы
  1. ☑ — чекбокс выбора для массовых операций.
  2. — внутренний ID абонента.
  3. ФИО — клик открывает карточку.
  4. Дог. — номер договора.
  5. Тариф — с иконкой 💼 для бизнес-тарифов.
  6. Адрес — короткий формат (через фильтр home_short: ВО → Волгоградская обл., Влг → Волгоград).
  7. Контакты — иконки ✉ / 📞 с tooltip-значениями. Серая иконка = пустое поле.
  8. Баланс — баланс счёта. Рядом 🤝 при активном обещанном платеже.
  9. Подкл. — дата подключения.
  10. 📡 — флаг «онлайн сейчас» (RADIUS-сессия < 15 мин).
  11. Статус — цветной бейдж: Активен / Заблок. / Удалён.
  12. ℹ Подробнее — кнопка открывает drawer-панель без перехода на полную карточку.

Подсветка строк: красный фон — заблокированные за неуплату, синий — soft-deleted (видны только администратору), оранжевая полоса слева — глубокий долг (более одной месячной абонплаты).

Бесконечная подгрузка

Папка не использует пагинацию. При прокрутке вниз автоматически догружается следующая порция (по 100 записей) через AJAX-endpoint /admin/Abonents/<id>/children_json/. На больших папках (1000+ абонентов) загрузка остаётся плавной — браузер рендерит только видимый кусок.

Массовые действия и экспорт

Чекбокс в первой колонке + «Выбрать все на странице» в шапке. После выделения появляется меню «Действия (N)» — те же 11 операций, что и в глобальном поиске: включить / выключить / удалить / изменить тариф / точку подключения / перенести в другую папку / добавить услугу / бонус / финоперацию / лояльность / отправить сообщение. Destructive-операции защищены confirm-модалкой с предпросмотром имён, для большинства — кнопка «Откатить» в баннере на 1 час.

Экспорт CSV — кнопка «📥 CSV» в шапке выгружает текущий срез папки (/admin/Abonents/<id>/export/): UTF-8 BOM, разделитель «;» для Excel RU. Колонки совпадают с видимыми в таблице плюс расшифровка тарифа, оператора, баланс в рублях. Учитываются текущий фильтр-чип и текстовый поиск.

Навигация по подпапкам

Если у текущей папки есть подпапки — они выводятся отдельным блоком над таблицей абонентов: карточки с именем подпапки и счётчиком абонентов внутри (включая глубоких потомков). Клик переходит в подпапку (отдельный URL). Хлебные крошки в шапке страницы показывают полный путь от корня (#9001 → город → улица → дом), кликабельны.

Глобальный поиск (Абоненты → 🔍 Глобальный поиск, URL /admin/Abonents/search/) — плоский список всех абонентов без иерархии папок, с расширенными фильтрами и массовыми операциями. Папки в выборку не попадают (is_folder=false).

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

Глобальный поиск абонентов

Содержание раздела
KPI-бейджи в шапке

Над таблицей выводится 4 счётчика по результатам текущей выборки: Активных (enabled=true) / Сейчас онлайн (RADIUS-сессия < 15 мин) / Выкл (enabled=false) / Всего. Цифры пересчитываются на каждой смене фильтра.

Чипы быстрых фильтров (7 штук)

Чипы взаимоисключающие — активен может быть только один. Кликом по уже активному чипу фильтр сбрасывается. Состояние записывается в URL (?chip=debtors) — ссылку можно отправить коллеге.

ЧипИконкаЧто показываетSQL-условие
Должники Абоненты с отрицательным балансом счёта admin_account.ostatok < 0
Бизнес💼 Юридические лица abonents.company = true
Без email Не указан e-mail для уведомлений email IS NULL OR email = ''
Без телефона📵 Не указан номер телефона (SMS не уйдут) sms IS NULL OR sms = ''
Онлайн📡 Сейчас в RADIUS-сессии (< 15 минут с last update) USERS_RADIUSAUTH.LOGGED = 1 AND RADIUS_UPDATE >= now() - 15 min
Заблокированные🚫 Любая активная блокировка (admin / negbal / own) есть запись в ABONENTS_BLOCK с активным флагом
Обещ. платёж🤝 Активен обещанный платёж (Usluga 30353) users_usluga.usluga_id = 30353 AND enabled = true
Multi-select фильтры
Текстовый поиск

Поле в шапке таблицы. Серверный поиск по: ФИО, номер договора, e-mail, телефон, адрес (город / улица / дом / квартира). Минимум 2 символа, debounce 300 мс. Шорткат Ctrl+K ставит фокус в поле поиска из любого места страницы.

Колонки таблицы (12)
  1. ☑ — чекбокс выбора для bulk-операций.
  2. — внутренний ID абонента.
  3. ФИО — имя, кликабельно (открывает карточку).
  4. Дог. — номер договора.
  5. Тариф — название текущего тарифа; для бизнес-тарифов иконка 💼.
  6. Адрес — короткий формат через фильтр home_short (ВО → Волгоградская обл., Влг → Волгоград, район сокращённо).
  7. Точка подкл. — название ConnectionPoint.
  8. Контакты — иконки ✉ / 📞 с tooltip-значениями. Серая иконка = не заполнено.
  9. Баланс — баланс счёта; рядом 🤝 при активном ОП.
  10. Подкл. — дата подключения.
  11. 📡 — флаг «онлайн сейчас» (RADIUS-сессия < 15 мин).
  12. Статус — цветной бейдж (Активен / Заблок. / Удалён).

Подсветка строк: красный фон — заблокированные, синий — удалённые, оранжевая полоса слева — глубокий долг (более одной месячной абонплаты).

Бесконечный скролл

Глобальный поиск не использует пагинацию: при прокрутке вниз подгружается следующая порция результатов. Папочные страницы (/admin/Abonents/<id>/) работают по тому же принципу через AJAX-endpoint /admin/Abonents/<id>/children_json/ по 100 записей за запрос.

Массовые действия — 11 операций

Выбираете абонентов галочками в первой колонке (или «Выбрать все» в шапке) — в шапке появляется выпадающее меню «Действия (N)» с дюжиной операций. Endpoint: POST /admin/Abonents/bulk_action/ (payload: action + ids[] + параметры).

ДействиеЧто делаетUndo
Включить (enable) Снимает флаг enabled=false, шлёт CoA-Disconnect для переподключения.
Выключить (disable) Ставит enabled=false, разрывает RADIUS-сессии.
Удалить (delete) Soft-delete: deleted=true, все Users отключаются, CoA-Disconnect.
Изменить тариф (change_tarif) Смена тарифа (Select2 в модалке). Срабатывает сразу, не «с 1-го числа».
Изменить точку подключения (change_cp) Перевешивает абонента на другую ConnectionPoint.
Перенести в папку (move_folder) Меняет parent_id в MPTT-дереве (Select2 по папкам).
Добавить услугу (add_service) Подключает разовую/периодическую услугу (Usluga из Select2).
Добавить бонус (add_bonus) Подключает бонус (компенсация / скидка с отрицательной суммой).
Добавить операцию (add_finop) Создаёт FinanceOperations на счёт (тип, сумма, описание).
Добавить лояльность (add_loyalty) Включает программу лояльности (AbonentsLoyalty).
Отправить сообщение (send_msg) Рассылка по выбранным каналам: e-mail / SMS / Push / Telegram.

Для destructive-операций (Включить / Выключить / Удалить / Изменить тариф / Перенести в папку) после выполнения в шапке появляется баннер «Откатить (доступно N мин)» с таймером 1 час. Клик отменяет изменение для всех затронутых абонентов через POST /admin/Abonents/bulk_undo/. Undo-токен хранится в sessionStorage браузера. Create-операции (Добавить услугу / бонус / финоперацию / лояльность / отправить сообщение) не откатываются — придётся вручную удалять созданные записи.

Подтверждение опасных операций

Удаление, отключение, включение и смена тарифа открывают модальное окно подтверждения: показывает превью первых 5 имён выбранных абонентов, общее их количество и требует ввести точное число выбранных записей для активации кнопки «Подтвердить». Это защита от случайной массовой операции по невнимательности.

CSV-экспорт

Кнопка «📥 CSV» в шапке выгружает текущий видимый срез (/admin/Abonents/search/export/): UTF-8 BOM, разделитель «;» для Excel RU. Колонки те же что в таблице плюс расшифровка тарифа, оператор и баланс в рублях.

Слайд-панель абонента (drawer)

В строках таблиц списков абонентов (папка, глобальный поиск, должники) у каждой записи справа есть кнопка ℹ Подробнее (на mobile — клик по имени). Клик открывает выезжающую справа панель (slide-panel / drawer) с краткой сводкой по абоненту — без ухода со списка и потери контекста (фильтры, прокрутка, выделение сохраняются).

Панель решает задачу «быстро глянуть и сделать», когда полная карточка с десятком вкладок избыточна. Типичные сценарии за 5–30 секунд: проверить баланс и активировать ОП, посмотреть статус сессии при жалобе на интернет, отправить SMS-напоминание, добавить комментарий-заметку для коллег.

Слайд-панель абонента — общий вид при открытии

Содержание раздела
Где доступна панель

Программный API из любой страницы: window.AbDrawer.open(abonentId).

Шапка с плашками статуса

Вверху панели — компактные плашки с ключевыми атрибутами абонента (видно с одного взгляда, без скролла):

ПлашкаЧто показываетЦвет
#<номер договора>Номер договора абонента
Активен / ОтключёнСостояние абонента целикомзелёный / жёлтый
Физ. лицо / БизнесТип клиентасерый
🔑 ЛогинОсновной логин учётной записи (скрыт на mobile)серый
🌐 IPIP-адрес учётной записисерый
🔓 Вкл. / 🔒 Выкл.Состояние учётной записи (отдельно от абонента)зелёный / жёлтый
📶 Онлайн / 🔌 ОфлайнСейчас в RADIUS-сессии или нетзелёный / жёлтый
🪪 СОРМ N/9Полнота персональных данных по СОРМ-требованиямзелёный (9/9) / жёлтый (5–8) / красный (0–4)

Шапка имеет фон заметно темнее тела (light #e8edf2 / dark #14171b) — оператор сразу различает «где статус, где данные».

Финансовый KPI-блок

Сразу под шапкой — KPI-сетка 2×2 с самыми важными цифрами:

Если активен обещанный платёж — отдельная плашка с суммой и датой окончания под KPI-сеткой.

Аккордеон 6 разделов и smart-defaults

Шесть разделов панели объединены в аккордеон — оператор сам открывает что нужно, не скроллит мимо. Состояние свёрнутости запоминается в localStorage отдельно по каждой секции — выбор оператора сохраняется.

РазделSmart-default (открыт если…)
👤 Учётные записиУчётка отключена (!primary_enabled) — оператор сразу видит проблему
🌐 Последняя сессияАбонент онлайн или есть активная сессия
📈 Платежи за 12 мес.Всегда свёрнут — открывается по запросу
📋 Последние операцииДолжник (balance < 0) — оператор смотрит откуда долг
📝 Комментарии операторовУже есть комментарии — значит история важна
🎧 ПоддержкаКлиент ждёт ответа в FreeScout (waiting_for_us > 0)

Если оператор однажды раскрыл/свернул секцию вручную — это его выбор сохраняется в localStorage и smart-defaults больше не перетирают.

Слайд-панель — все разделы аккордеона раскрыты

Раздел «Поддержка» (FreeScout)

Шестой раздел аккордеона — интеграция с FreeScout. Биллинг показывает все обращения абонента в техподдержку прямо в слайд-панели, без перехода в FreeScout:

Слайд-панель — раздел Поддержка с тикетами FreeScout

Форма отправки сообщений и табы каналов прилеплены ко дну панели (не уезжают при скролле истории сообщений). Оператор не скроллит чтобы написать SMS — форма всегда на виду, как в современных мессенджерах.

Sticky footer — форма отправки прилеплена ко дну при скролле

Каналы отправки (SMS / Email / Telegram / ЛК / ВК / Push)

Над формой ввода — chip-табы с каналами отправки. Показываются только доступные абоненту каналы:

При выборе таба Email над формой появляется поле «Тема письма» — если оператор не укажет, дефолтная тема возьмётся из первой строки текста (до 80 символов).

Табы каналов — выбран Email, появилось поле Тема письма

SMS-форма поддерживает дефолтный шаблон: при пустом тексте у должника автоматически отправляется напоминание «Договор XXX. Задолженность YYY ₽. После пополнения счёта доступ к услугам восстановится. Узнать баланс или взять ОП — отправьте «Баланс» или «Оп» в ответ на это сообщение. ЛК: lk.smit34.ru».

Для абонентов с положительным балансом дефолтный шаблон не отправляется — backend возвращает ошибку no_text_positive_balance, оператор должен ввести текст вручную.

Email в фирменном брендинге

Email-сообщения отправляются HTML в фирменном стиле — не plain text, а полноценное письмо с шаблоном emails/generic.html:

Авто-детект шаблона по теме/тексту: если содержит «обещанн», «оплат», «блокир», «возобновл», «баланс+низк» — выбирается специализированный пресет (promise_pay/payment_received/blocked/ unblocked/low_balance), иначе generic.

Доступность (WCAG colour independence)

Все цветовые статусы дублируются иконками — пользователи с deuteranopia (8% мужчин) различают информацию без полагания на цвет:

Mobile-режим

На mobile (≤600px) drawer занимает весь экран. Поведение одинаковое с desktop, только:

Слайд-панель на mobile — fullscreen с sticky footer

Тёмная тема

Drawer полностью адаптирован под тёмную тему. Header ещё темнее тела (#14171b vs body #1f2227), все секции и формы имеют тёмные варианты, footer-actions с своим bg.

Слайд-панель в тёмной теме

Карточка абонента

Переход кликом по строке абонента — открывается отдельная страница с вкладками Информация, История, Тариф и услуги, Бонусы, Финансы, Точки подключения, Учётные записи, RADIUS, СОРМ, Реквизиты, HelpDesk, Лояльность. Подробное описание каждой вкладки — в отдельном разделе 9. Карточка абонента.

Bulk-операции и Undo

Выберите абонентов галочками — появится плавающая панель действий внизу экрана.

Bulk-операции с абонентами

Доступные действия:

Для destructive-операций (delete, disable, enable, change_tarif, move_folder) после выполнения в шапке появляется баннер «Откатить (доступно N мин)» с таймером 1 час. Клик отменяет изменение для всех затронутых абонентов.

8.2. На карте

Карта абонентов в браузере (Leaflet + OSM / 2ГИС / Yandex). Каждый маркер — абонент или узел оборудования. URL: /admin/abonents/map/. Меню сайдбара: Абоненты → 🗺 На карте.

Карта абонентов

Возможности

Геокодирование адресов

Чтобы абонент появился на карте, его адрес (Homes.parent_id) должен иметь координаты lat и lon. Заполнение координат происходит автоматически через геокодеры — внешние сервисы, которые по строке адреса возвращают координаты.

Поддерживаются два провайдера:

ПровайдерГде настроитьЛимитыКоманда
DaData (рекомендуется для России) /admin/settings/integrations/?tab=geo«Адреса», поле DADATA_TOKEN. 10 000 запросов/день бесплатно (тариф «Бесплатный»), но не более 10 тыс. в месяц. manage.py geocode_homes_dadata [--re-geocode]
Яндекс.Геокодер (запасной) /admin/settings/integrations/?tab=geo«Адреса», поле YANDEX_GEOCODER_API_KEY. 25 000 запросов/день бесплатно (HTTP Геокодер). manage.py geocode_homes_yandex [--re-geocode] [--only-no-qc]

API-токены задаются в разделе «Настройки → Интеграции → География» (вкладка «География → Адреса»). Здесь же есть кнопка «Проверить» для теста подключения и счётчик геокодированных адресов.

Запуск геокодирования

На testbill сейчас геокодировано ~95% адресов (4045 из 4261), из активных абонентов ~90% на карте. Чтобы геокодировать новый адрес автоматически — введите его в форме абонента, и при следующем прогоне команды (или nightly Celery-задаче) он попадёт на карту. Запустить геокодирование вручную:

docker compose exec web python manage.py geocode_homes_dadata
# или принудительно для всех (включая уже геокодированные):
docker compose exec web python manage.py geocode_homes_dadata --re-geocode

Команда:

Маркеры абонентов на адресах с qc_geo=2 (только город) на карте отображаются как «приблизительные» — несколько абонентов одного города могут лечь на одну точку. Для повышения точности проверьте, что у адреса заполнены поля «Улица» и «Дом».

Reverse-геокодинг (заполнение города/района по координатам)

Если у адреса есть координаты, но не заполнен город или район (после миграции из старых биллингов это бывает), используйте команду manage.py enrich_homes — она через Яндекс.Геокодер (обратное направление: координаты → адрес) восстанавливает текстовые поля.

Тайловый слой (фон карты)

Подложка карты выбирается на той же вкладке «География → Карта»: OpenStreetMap (бесплатно), 2ГИС (требует ключ), Yandex Maps. Дефолт — OSM. Стиль карточки в /admin/settings/integrations/?tab=geo показывает количество геокодированных адресов и точность (% домов с qc_geo=0).

Обещанный платёж (Promise Pay) — назначение и роль

Содержание раздела

Обещанный платёж (ОП) — короткий «кредит» абоненту: биллинг временно увеличивает его баланс, чтобы интернет работал ещё несколько дней до фактической оплаты. Реализован через Usluga id=30353 с system_type=11. Ключевые поля абонента: Abonents.promise_pay (сумма ₽) и promise_date_end (дата истечения).

Что такое ОП и зачем нужен

Сценарий типичного использования:

  1. Абонент в командировке, ушёл в минус по балансу. Биллинг блокирует (b_negbal);
  2. Абонент через ЛК / SMS / звонок в поддержку запрашивает ОП;
  3. Биллинг временно зачисляет ему ₽ на 5 дней (срок настраивается);
  4. Финансовая блокировка снимается, интернет возвращается через CoA Disconnect (build 258);
  5. Через 5 дней абонент пополняет счёт. Иначе ОП истекает, баланс возвращается к минусу, блокировка повторяется.
Экономика (build 656)
ПараметрЗначениеГде настраивается
Лимитmax(месячная цена тарифа, |долг|+1₽) с cap 2× тарифаАвтоматически. Override: LK_PROMISE_PAY_MAX_LIMIT_OVERRIDE
Длительность5 днейUsluga.end_count_days=5. Override: LK_PROMISE_PAY_MAX_DURATION_DAYS
Стоимость в день30 ₽/день — списывается ежедневно через billing_worker, если включеноUsluga 30353.price + флаг LK_PROMISE_PAY_DAILY_CHARGE (default 1)
Cooldown30 дней между двумя ОПLK_PROMISE_PAY_COOLDOWN_DAYS (default 30 в build 656)
Min age30 дней с момента создания договораLK_PROMISE_PAY_MIN_AGE_DAYS (default 30 в build 656)
Авто-погашение при оплатеЕсли YooKassa/W1/Generic webhook зачислил достаточно — ОП авто-отменяетсяLK_PROMISE_PAY_AUTO_CLOSE_ON_PAYMENT (default 1)
Уведомление за N часовPush за 24ч до окончания + при истеченииMOBILE_PUSH_PROMISE_PAY_EXPIRE_HOURS (default 24)
Логика лимита (build 656): раньше лимит ОП был ровно цена тарифа. Если долг превышал цену — баланс не выходил в плюс и интернет не включался («активировал ОП, а интернета нет»). Теперь лимит масштабируется до покрытия долга, но ограничен 2× тарифа — защита от злоупотреблений через SMS. Если долг больше cap — клиент получает ОП, но баланс остаётся отрицательным; SMS-ответ предупреждает «Баланс X ₽ — недостаточно, требуется доплата».

Примеры (тариф 500 ₽):
Eligibility — 4 политики

Перед активацией _check_promise_pay_eligibility() в lk/views/promise_pay.py проверяет:

  1. LK_PROMISE_PAY_MIN_DEBT — минимальный долг (например, ОП доступен только при балансе < -100 ₽);
  2. LK_PROMISE_PAY_MAX_LIMIT_OVERRIDE — макс. лимит ОП (если задан, перебивает расчёт по тарифу);
  3. LK_PROMISE_PAY_MIN_AGE_DAYS — минимальный «возраст» абонента (с момента create_date). Защита от мошеничества: создал договор и сразу взял ОП;
  4. LK_PROMISE_PAY_COOLDOWN_DAYS — пауза между ОП. Защита от циклов «брать ОП каждую неделю».

Все политики — настройки SystemSettings. Если значение = 0 — политика отключена. По умолчанию все включены.

Что происходит при активации

В одной транзакции:

  1. Создаётся UsersUsluga(usluga_id=30353, system_type=11, end_time=now+5дней, limit=<сумма ОП>);
  2. Создаётся FinanceOperations(op_type_id=31, op_summa=+limit, descr='[ЛК/SMS/Mobile] Обещанный платёж...');
  3. abonent.account.ostatok += limit (баланс пополняется);
  4. abonent.promise_pay = limit, abonent.promise_date_end = now+5дней — для бейджа в UI и баннера;
  5. Если был AbonentsBlock(b_negbal=true) — удаляется, Abonents.enabled=true;
  6. CoA Disconnect на NAS (build 258) — текущая Reject-сессия разрывается, абонент переавторизуется, RADIUS отвечает Accept → интернет работает в течение секунд;
  7. Аудит — запись в AUDIT_OPERATIONS с подробной descr.
Каналы активации
КаналURL/действие
ЛК абонента/lk/promise_pay/ — кнопка с подтверждением
Mobile AppPOST /mobile-api/v1/finance/promise_pay + GET /finance/promise_pay
SMS-командаТекст «ОП» → биллинг шлёт условия → текст «ДА» в течение 10 мин (build 539)
АдминкаКарточка абонента → вкладка «Услуги» → кнопка «+ Обещанный платёж» (modal с условиями)
REST API v2POST /rest_api/v2/promise_pay/ для внешних интеграций
Завершение ОП — 4 сценария (build 656)

Есть четыре пути завершить активный ОП — все приводят к одному финалу: возврат лимита через обратную FinOp, очистка promise_pay/promise_date_end, удаление UsersUsluga, и при отрицательном балансе — авто-блок.

СценарийТриггерРеализация
A. Авто-погашение при оплате Webhook YooKassa/W1/Generic зачислил сумму, достаточную чтобы баланс после возврата лимита остался ≥0 lk/services/payment.py::_credit_abonent после успешного зачисления вызывает abonent.del_promise_pay(reverse_finance=True, reason='погашен оплатой ...'). Управляется LK_PROMISE_PAY_AUTO_CLOSE_ON_PAYMENT
B. Истечение по таймеру Abonents.promise_date_end < now Celery beat check_expired_promises (раз в 15 мин). Вызывает del_promise_pay(reverse_finance=True, reason='истёк срок'). Если баланс < 0 — ставит b_negbal через abonent.block()
C. Ручное удаление через UI Оператор удаляет UU обещанного платежа (кнопка корзины во вкладке «Услуги») uslugi_delete_ajax детектирует system_type=11 или usluga_id ∈ {-3,-4} → выполняет тот же reverse-сценарий
D. Тогглер «Выкл.» во вкладке «Услуги» build 656 Оператор переключает UU.enabled с True на False uslugi_edit_ajax детектирует переход True→False для ОП-услуги → вызывает del_promise_pay(reverse_finance=True, reason='отключено вручную'). До build 656 — просто переключал флаг, поля абонента оставались, баннер «ОП активен» висел.
Дневная плата 30 ₽/день (build 656)

Услуга 30353 имеет price=30 ₽, is_periodic=true, every_day=true. До build 656 эти поля были декоративными — при активации UU.sched_date не устанавливался, billing_worker услугу пропускал, дневная плата фактически не списывалась.

В build 656 при активации:

  1. Если LK_PROMISE_PAY_DAILY_CHARGE=1UU.sched_date = now+1d 09:00;
  2. Каждый день в 09:00 billing_worker списывает Usluga.price (30 ₽) с лицевого счёта;
  3. Запись FinanceOperations(op_type=32, descr='Списание абонплаты ...');
  4. sched_date сдвигается на следующий день — пока ОП активен, плата идёт ежедневно;
  5. При завершении ОП (любой из 4 сценариев) UU удаляется → списания прекращаются.

Чтобы выключить дневную плату: LK_PROMISE_PAY_DAILY_CHARGE=0 в /admin/settings/system/ + переименовать услугу 30353 (убрать «30 руб/день» из названия).

Уведомления
Audit (build 655)

Все операции с ОП пишутся в AUDIT_OPERATIONS с TABLE_NAME='USERS_USLUGA' через billing.services.audit_log.write_audit():

SMS-flow «ОП → ДА» (build 539): двухэтапная активация через SMS защищает от случайного нажатия. Сначала «ОП» проверяет 4 правила (eligibility), отправляет условия и сохраняет pending-state в Django cache на 10 минут. Только при получении подтверждения «ДА» (или «да», «yes», «ok», «+») биллинг активирует ОП. Pending-state хранится по последним 10 цифрам номера.
Сводка настроек ОП (build 656)

Все настройки в SystemSettings (/admin/settings/system/):

КлючDefaultНазначение
LK_PROMISE_PAY_MIN_DEBT0Минимальный долг (₽) для активации. 0 = выкл
LK_PROMISE_PAY_MIN_AGE_DAYS30Минимальный возраст абонента (дни)
LK_PROMISE_PAY_COOLDOWN_DAYS30Интервал между двумя ОП (дни)
LK_PROMISE_PAY_MAX_DURATION_DAYS0Override длительности (0 = берём из Usluga.end_count_days)
LK_PROMISE_PAY_MAX_LIMIT_OVERRIDE0Override лимита (0 = автоматический расчёт max(тариф, долг+1))
LK_PROMISE_PAY_DAILY_CHARGE1Списывать Usluga.price ежедневно через billing_worker
LK_PROMISE_PAY_AUTO_CLOSE_ON_PAYMENT1Авто-погашение при достаточной оплате
LK_PROMISE_PAY_NOTIFY_BEFORE_HOURS24Окно уведомления перед истечением

Defaults применяются миграцией 0127_promise_pay_defaults. UPSERT — существующие значения не перезаписываются, на проде с 0 (выкл) останется 0 пока админ сам не включит.

Блокировки (AbonentsBlock) — назначение и роль

Содержание раздела

AbonentsBlock — таблица записей о блокировках абонента. Один абонент может иметь одну активную запись с несколькими булевыми флагами (один флаг = одна причина блокировки). Хранится в ABONENTS_BLOCK.

Три типа блокировок
ФлагКто ставитКогда
b_negbal 🔴 ФинансоваяСистема (Celery / accounts_payment_ajax)Баланс ушёл ниже LIMIT на счёте
b_admin 🟠 АдминистративнаяОператорРучное действие — нарушение договора, спам, заявление абонента
b_own 🟡 ДобровольнаяСам абонентЧерез ЛК / SMS-команду «ВЫКЛ» — пауза услуг (отпуск, командировка)
Блокировка vs Abonents.enabled

Это два разных механизма, иногда путают:

ПолеЧто этоКто меняет
Abonents.enabledГлавный «выключатель» абонента. Если false — RADIUS отвечает Reject, услуги не работаютМетод block() ставит в false, unblock() — в true (если других блокировок нет)
AbonentsBlockЗапись о причине блокировки (флаги b_negbal/b_admin/b_own)process_blocks Celery, accounts_payment_ajax, ручные действия

Корректная схема — блокировка всегда сопровождает enabled=false. Рассинхрон («есть запись блокировки, но enabled=true» или наоборот) — это баг, чинится через метод process_blocks или ручную правку через UI.

Жизненный цикл блокировки
  1. Создание:
    • process_blocks (Celery, раз в 5 мин) — сравнивает OSTATOK с LIMIT у всех абонентов и создаёт b_negbal;
    • accounts_payment_ajax (build 541) — мгновенно блокирует если расход через UI ушёл в минус;
    • Кнопка «Заблокировать» в карточке абонента — ставит b_admin;
    • Abonents.add_promise_pay() при истечении срока ОП — возвращает b_negbal если баланс остался отрицательный.
  2. Активные действия при блокировке (см. Abonents.block()):
    • Создаётся / обновляется запись в ABONENTS_BLOCK;
    • Abonents.enabled = false;
    • Все Users.enabled = false (учётки) → RADIUS отвечает Reject при следующей авторизации;
    • На все NAS параллельно отправляется CoA Disconnect (см. ниже) — текущие сессии разрываются;
    • Пишется аудит-запись в AUDIT_OPERATIONS с table_name='ABONENTS_BLOCK';
    • Опционально — IPTV sync (отключить пакеты в TVIP / LFStream).
  3. Снятие:
    • process_blocks при пополнении баланса убирает b_negbal;
    • accounts_payment_ajax (build 541) — мгновенно снимает b_negbal когда баланс >= 0;
    • Кнопка «Разблокировать» в карточке — снимает b_admin;
    • SMS-команда «ВКЛ» снимает b_own;
    • Если все флаги в записи стали falseAbonentsBlock удаляется, Abonents.enabled=true, услуги возвращаются.
CoA Disconnect — что происходит с активной сессией

Чтобы блокировка отрабатывала немедленно (а не через 5-15 мин когда сессия сама закроется), биллинг шлёт CoA Disconnect:

Без CoA Disconnect блокировка работала бы только при следующей перерегистрации абонента — а это иногда раз в сутки. С CoA — за 5-10 секунд.

Build 540-541: все блокировки и снятие блокировок теперь пишутся в AUDIT_OPERATIONS через Abonents.block() / Abonents.unblock(). Раньше прямой AbonentsBlock.objects.create() в нескольких местах не писал аудит и не отключал Users — это было исправлено в build 541. Все события блокировок теперь видны во вкладке «Аудит» карточки абонента.

Лицевые счета (AdminAccounts) — назначение и роль

Содержание раздела

Лицевой счёт (AdminAccounts) — это финансовый «кошелёк» абонента в биллинге. Хранит баланс, бухгалтерские дебет/кредит, лимит блокировки, валюту. Хранится в таблице admin_accounts.

Зачем нужен лицевой счёт

Счёт решает четыре задачи:

  1. Учёт денег абонента — пополнения и списания идут через счёт. Текущий баланс — поле OSTATOK;
  2. Финансовая блокировка по порогу — если OSTATOK < LIMIT, Celery-задача process_blocks блокирует абонента (см. Блокировки);
  3. Бухучёт — поля DEBIT (поступления) и CREDIT (расходы) накапливают полные суммы для отчётности;
  4. Группа абонентов на одном счёте — можно привязать несколько Abonents к одному AdminAccounts (через Abonents.ACCOUNT_ID) для семейных тарифов или корпоративных схем.
Поля и связи
ПолеНазначение
OSTATOKТекущий баланс (×10^10, см. ниже)
DEBITСумма всех поступлений за всё время
CREDITСумма всех списаний за всё время
LIMITМинимально допустимый баланс (если UNLIMITED=false) — ниже идёт блокировка
UNLIMITEDБезлимитный счёт (не блокировать по балансу)
WARNПорог предупреждения: при OSTATOK < WARN отправляется уведомление
BONUS_SCOREБонусные баллы (программа лояльности)
CURRENCY_IDВалюта счёта (FK → currencies)
SLEEP_DATE«Заморозить» счёт до даты (счёт неактивен)
DB_MONEY_KOEF — почему ×10^10

Все денежные поля биллинга (OSTATOK, DEBIT, CREDIT, LIMIT, op_summa в FINANCE_OPERATIONS, price/summa в usluga) хранятся как целые числа, умноженные на 10^10. Это позволяет избежать ошибок округления плавающей запятой и хранить суммы с точностью до 10 знаков после запятой.

Кто меняет баланс
ИсточникЧто делает
billing_worker (Celery)Ежемесячное списание услуг по тарифу (op_type=32). Минусит OSTATOK
accounts_payment_ajaxРучное пополнение/списание оператором через UI «Касса»
YooKassa / W1 webhooksАвтоматическое пополнение после онлайн-оплаты
add_promise_payАктивация ОП — плюс к балансу + UU 30353
stornoСторнирование операции — обратное движение баланса с флагом storno=true
Каждое изменение баланса сопровождается записью в FINANCE_OPERATIONS и AUDIT_OPERATIONS (build 540). Не правьте OSTATOK SQL-запросом — это нарушит инварианты OSTATOK = DEBIT - CREDIT и историю операций. Всегда через UI или сервисный слой (_create_finop_atomic).

Финансовые операции (FinanceOperations) — назначение и роль

Содержание раздела

Финансовая операция (FinanceOperations) — каждое движение денег по лицевому счёту: пополнение, списание, обещанный платёж, начисление бонуса. Хранится в таблице finance_operations.

Зачем нужны финоперации
  1. Журнал движения денег — каждое изменение баланса должно сопровождаться записью FinOp. Это аудиторский след: всегда понятно почему баланс такой, как сейчас;
  2. Отчётность — отчёты «сколько собрано за месяц», «топ должников», «расход на услуги» строятся через агрегаты по FinanceOperations;
  3. Сторнирование — при ошибке оператора можно создать обратную операцию, не теряя истории. Поле storno=true + связь related_operation_id на оригинал;
  4. Идемпотентность платежей — для онлайн-оплат через webhook (YooKassa/W1) сверяется уникальность по `[txn:<ID>]` в descr — повторный webhook не создаст дубль.
Поля и связи
ПолеНазначение
OP_IDPK операции
OP_DATEДата операции (когда деньги фактически прошли). Может отличаться от SYSTEM_DATE (когда запись создана)
OP_SUMMAСумма ×10^10 (положительная = приход, отрицательная = расход). Знак определяется типом операции через op_sign
OP_TYPE_IDFK → fin_types — категория операции (см. ниже)
ABONENT_IDFK → abonents — кому принадлежит операция
ACCOUNT_IDFK → admin_accounts — на какой лицевой счёт
OWNER_IDFK → auth_user — какой оператор провёл (NULL для автоматических операций)
DESCRСвободный текст с деталями. Для платежей содержит [txn:<ID>]
NUMBERНомер документа (квитанция, чек, № платёжки)
STORNOtrue, если эта операция — обратная сторно к другой
RELATED_OPERATION_IDFK на саму себя — связь с оригиналом для пары storno↔original
USLUGA_IDFK → usluga — какая услуга была списана/начислена (если применимо)
AA_OSTATOK / AA_DEBIT / AA_CREDITСнимок состояния счёта на момент операции (для отчётов)
Типы операций

Справочник fin_types определяет:

Основные типы (из чтения кода):

op_type_idНазначениеЗнак
31Обещанный платёж — активация ОП (плюс к балансу)+
32Тарифное списание (billing_worker, ежемесячно)
другиеРучная проводка, единая касса, YooKassa, W1, корректировки и т.д. — справочник в /admin/settings/system/?tab=params±
Кто создаёт финоперации
ИсточникЧто делает
billing_worker (Celery, ежедневно)Списание тарифа (op_type=32) у всех активных абонентов
finops_list.py (UI «Финансовые операции»)Прямое CRUD оператором — модалка add/edit/delete
abonents.py:add_fin_op (карточка)Ручная проводка из карточки абонента кнопкой «+ Финоп»
accounts_list.py:_create_finop_atomicКасса — пополнение/корректировка через UI «Лицевые счета»
accounts_storno_last_ajaxСторно последней операции — кнопка в модалке счёта
lk/services/payment.py:_credit_abonentWebhook YooKassa/W1/Generic — пополнение после онлайн-оплаты
lk/views/promise_pay.pyАктивация обещанного платежа (op_type=31)
billing/services/sms_inbound.pySMS-команды (ОП через SMS — build 539)
Сторнирование

Сторно — это создание обратной операции, не удаление оригинала:

  1. Оператор открывает модалку счёта → клик «🔄 Сторно последней» (с подтверждением);
  2. Создаётся новая FinanceOperations с op_summa = -original.op_summa, тот же op_type, storno=true, related_operation_id = original.op_id;
  3. Оригинал помечается storno=true (для UI: зачёркнутый текст);
  4. Баланс счёта корректируется на сумму сторно;
  5. Аудит-запись (build 540) фиксирует обе таблицы — FINANCE_OPERATIONS (с привязкой к абоненту) и ADMIN_ACCOUNTS (с привязкой к счёту).
Build 540: аудит ВСЕХ операций. Раньше при удалении/изменении/создании FinOp в части мест аудит не писался. Теперь все 5 точек создания/удаления (UI модалка, REST API, ChangeView, storno, billing_worker) обязательно создают запись в AUDIT_OPERATIONS с table_name='FINANCE_OPERATIONS' и object_id=op_id. Видно во вкладке «Аудит» карточки абонента с drill-down на саму операцию.

8.3. Счета

URL: /admin/Abonents/AdminAccounts/

Лицевой счёт — основной финансовый объект абонента. На нём хранится остаток, дебет, кредит, стоплата и привязка к валюте. К одному счёту может быть привязан один или несколько абонентов (Abonents.account_id → AdminAccounts, FK PROTECT). Все финансовые операции (FinanceOperations) ссылаются на счёт напрямую, что позволяет вести историю по счёту независимо от смены абонента.

Лицевые счета — список

Список счетов

Модалка счёта

Клик по строке или по кнопке-карандашу открывает модальное окно (modal-xl) с левой навигацией на 5 вкладок. URL-параметр ?edit=<id> автоматически открывает модалку при загрузке страницы (для глубоких ссылок). Старая страница /admin/Abonents/AdminAccounts/<id>/ редиректит 302 на список с открытой модалкой.

Вкладка «Баланс»

Модалка счёта — вкладка Баланс

Вкладка «Параметры»

Модалка счёта — вкладка Параметры

Лимиты и пороги:

Вкладка «Абоненты»

Модалка счёта — вкладка Абоненты

Список привязанных абонентов с бейджем «основной» (первый по ID), статусом и кнопками открытия профиля и отвязки. Кнопка Привязать открывает подмодалку с поиском (live-search через REST API). Если выбранный абонент уже привязан к другому счёту — показывается чекбокс Перенести.

Вкладка «Финансовые операции»

Модалка счёта — вкладка Финансовые операции

Последние 50 операций по счёту с цветовым кодированием:

Ссылка Все операции по счёту ведёт на отдельный раздел Финансовые операции с фильтром ?account_id=<id>.

Вкладка «Уведомления»

Модалка счёта — вкладка Уведомления

Последние 20 SMS / Email / Push сообщений по абонентам этого счёта (MsgStack, поля head_txt, text, send_date, флаги email_done/sms_done/push_done и др.). Read-only.

Пополнение / Корректировка / Сторно

На вкладке «Баланс» — три кнопки финансовых действий, каждая открывает подмодалку поверх главной (stacked modal через z-index патч).

Пополнение

Подмодалка пополнения счёта

Поля: Сумма (₽, обязательно), Тип операции (фильтрованы по op_sign > 0 — только приходные), Дата, Номер документа, Описание. Дополнительно: ID транзакции для идемпотентности — если задан и уже встречается в БД, повторный POST возвращает существующий op_id без двойного зачисления.

Live-preview под формой показывает: текущий баланс, сумму операции, новый баланс. Цветовое кодирование зелёный/красный.

Серверная логика (_create_finop_atomic): SELECT FOR UPDATE на счёт, инкремент ostatok, создание FinOp в одной транзакции, запись в audit (ADMIN_ACCOUNTS + FinanceOperations).

Корректировка

Подмодалка корректировки счёта

Тот же endpoint, но без фильтра типов — оператор сам выбирает из всех 30+ типов (например, «Корректирующее списание» или «Ручная проводка»). Знак суммы определяется типом (op_sign): если выбран расходный тип, введённая положительная сумма автоматически инвертируется. На скриншоте — отрицательная сумма с предпросмотром нового баланса.

Подмодалка привязки абонента к счёту

Открывается из вкладки «Абоненты» по кнопке + Привязать. Поле поиска ищет абонента по ФИО, договору, телефону, email через REST API (с debounce 300мс). Результаты отображаются с бейджами «на счёте #N» если абонент уже привязан где-то ещё. Чекбокс Перенести появляется только при выборе такого абонента и подтверждает перевод с другого счёта.

Сторно последней

Confirm-диалог с реквизитами последней операции (op_id, сумма, описание). После подтверждения создаётся обратная FinOp, у оригинала ставится storno=True, остаток корректируется. Сторнированную операцию повторно сторнировать нельзя.

Защита от удаления

Hard-delete счёта возможен только если соблюдены оба условия:

Иначе сервер возвращает HTTP 400 с понятной подсказкой («Сначала отвяжите N абонентов…» / «У счёта M операций. Удаление запрещено для сохранения истории»).

Audit-trail

Каждое действие записывается в AuditOperations (table_name='ADMIN_ACCOUNTS') с реквизитами оператора и diff-описанием:

REST endpoints

URLМетодНазначение
/admin/Abonents/AdminAccounts/GETHTML-страница списка
/admin/Abonents/accounts_json/GETJSON: список с фильтрами и пагинацией (поля q, chip, page, per_page, sort)
/admin/Abonents/accounts_get/<id>/GETJSON: счёт + абоненты + последние 50 финопов + 20 уведомлений
/admin/Abonents/accounts_crud/[<id>/]POSTCreate/Update/Delete (флаг _delete=1)
/admin/Abonents/accounts_payment/<id>/POSTПополнение / Корректировка
/admin/Abonents/accounts_storno/<id>/POSTСторно последней операции
/admin/Abonents/accounts_link/<id>/POSTПривязать абонента (force=1 — перевод с другого счёта)
/admin/Abonents/accounts_unlink/<id>/<abonent_id>/POSTОтвязать абонента
/admin/Abonents/accounts_bulk/POSTBulk: change_currency / set_limits / delete_orphans
/admin/Abonents/accounts_csv/GETCSV-экспорт (UTF-8 BOM, ;) — по фильтрам или ?ids=1,2,3

Денежные правила

Все суммы в БД хранятся как integer × 10^10 (DB_MONEY_KOEF = Decimal('10000000000')). Конвертация происходит автоматически в MoneyField / MoneyFormField: prepare_value() делит на коэффициент при отображении, clean() умножает обратно при сохранении. На уровне UI и REST API всегда работаем в рублях с двумя знаками после запятой.

8.4. Финансовые операции

URL: /admin/Abonents/FinanceOperations/

Финансовая операция (FinanceOperations, таблица finance_operations) — атомарная запись в журнале движения денег. Привязана к лицевому счёту (account_id → AdminAccounts) и к абоненту (abonent_id → Abonents). Тип операции (op_type → FinTypes) определяет знак (op_sign: +1 приход, −1 расход). Сумма хранится в БД как op_summa × 10^10.

Раздел построен по тому же паттерну, что и Счета (build 101): кастомная страница с AJAX-пагинацией и модальным редактированием. Это самый быстрый способ работать с журналом, в котором сотни тысяч записей.

Список операций

Финансовые операции — список с фильтрами

Модалка финансовой операции

Modal-lg с компактной формой (паттерн build 101):

Удаление через кнопку «Удалить» внутри модалки → confirm. Удаление операции НЕ откатывает остаток счёта (нет автоматической компенсации). Для отмены влияния — используйте сторно в карточке счёта (создаст обратную операцию + флаг storno=True), а не удаление.

Типы операций (FinTypes)

Справочник типов хранится в fin_types. Поле op_sign определяет знак суммы:

UI пополнения счёта (см. 7.3. Счета → Пополнение) фильтрует список типов по op_sign > 0; UI корректировки счёта показывает все типы, оператор сам выбирает направление.

Редактирование справочника

URL: /admin/dictionary/FinTypes/ (раздел Справочники → Типы фин. операций, build 508).

Список типов финансовых операций

Список с поиском, чипами-фильтрами по знаку (Все / Приход / Расход / Нейтр. / Основные) и счётчиком использований в журнале FinanceOperations. Создание и редактирование — в модальном окне с радиогруппой Приход (+1) / Нейтр. (0) / Расход (−1).

Модалка редактирования типа с warning об использовании

На скриншоте — редактирование типа «Корректирующее списание» (#43, расход): warning «использован в 5411 операций» виден сверху, кнопка Удалить скрыта (нельзя удалить связанный тип).

Поля:

Защита удаления: тип нельзя удалить, если он использован хотя бы в одной финансовой операции. Сервер вернёт ошибку «Тип использован в N финансовых операций. Удаление запрещено для сохранения истории». Это нужно потому что некоторые type_id жёстко зашиты в коде (например, 23 = YooKassa в webhook'ах, 32 = ежемесячное списание тарифа в billing_worker) — их удаление сломает интеграции.

Audit-trail: каждое создание / изменение / удаление записывается в AuditOperations (table_name='FIN_TYPES', object_id=type_id) с указанием оператора и описанием изменения.

Когда добавлять новый тип: при подключении новой платёжной системы (например, СБП, T-Bank) или появлении специфической бухгалтерской операции (взаимозачёт между операторами, перерасчёт курсов и т.п.). Перед использованием нового type_id в коде webhook / FinanceOperations.objects.create — проверьте что значение возвращается из FinTypes.objects.get(...), иначе при удалении/переименовании типа интеграция сломается.

Связи и снапшот

Помимо account, abonent, op_type, у операции есть:

Онлайн-касса (ФР)

Поля для интеграции с ОФД и кассовой техникой:

Настройки фискализации — в /admin/settings/atol_config/.

Внешние операторы

Для операций, пришедших через webhook платёжных систем (/admin/settings/payment/):

Идемпотентность по operator_pay_id_str — повторный webhook на ту же транзакцию не создаёт дубль операции.

Синхронизация с 1С

REST endpoints

URLМетодНазначение
/admin/Abonents/FinanceOperations/GETHTML-страница списка
/admin/Abonents/finops_json/GETJSON: список с фильтрами и пагинацией (q, op_type, date_from, date_to, page, per_page) или ?_id=<op_id> для одной записи
/admin/Abonents/finops_crud/[<op_id>/]POSTCreate / Update / Delete (флаг _delete=1)
/rest_api/v2/FinanceOperations/GET / POSTПолный REST API v2 (CRUD)

Чем отличается от Журнала платежей?

В системе исторически было два раздела:

Оба читают одну и ту же таблицу finance_operations; разница только в UI и наборе фильтров.

9. Карточка абонента

URL: /admin/Abonents/<id>/. Открывается кликом по строке абонента в любом списке (папка, глобальный поиск, модалка счёта). Это центральная рабочая страница оператора — здесь видны все аспекты абонента и доступны все действия.

Карточка абонента

Шапка карточки

Сверху страницы — фиксированная панель с:

Все destructive-действия (Заблокировать / Удалить) защищены Bootstrap-confirm с превью последствий (паттерн build 335).

Быстрый доступ (под шапкой)

Три блока с самой важной информацией без переключения вкладок:

Вкладки

13 вкладок с подробной информацией. Часть из них объединена в группы (build 71): «Учётные записи + RADIUS», «Точки подключения + Оборудование» — соседние вкладки навигируются стрелками.

Информация

Основные поля абонента в двух колонках:

Mini-блок СОРМ под контактами показывает паспорт + ИНН с кнопкой быстрого редактирования (без перехода на отдельную вкладку). Для юр.лиц — ИНН/КПП/ОГРН + В лице/Директор.

Блок «Финансовая информация»

Под основной информацией абонента отдельная карточка с расчётами по балансу. Состоит из двух частей: таблица балансов и параметры порогов/прогнозов.

Таблица балансов

Три варианта расчёта с разделением списаний на предоплату и постоплату:

СтрокаФормулаЧто значит
Бух.admin_accounts.ostatok / 10¹⁰Бухгалтерский баланс — фактическое значение лицевого счёта.
ТекущийБух − Расход предоплата + ПриходС учётом обещанных платежей и предстоящих списаний за уже оказанные услуги.
На конец месяцаТекущий − Расход постоплатаПрогноз остатка после всех списаний до конца расчётного периода.

Колонки:

Параметры и прогнозы
ПолеНазначение
Порог предупр.Если ostatok < threshold — отправляется уведомление через MsgStack (Email/SMS/Push).
Порог откл.Если ostatok < thresholdbilling_worker ставит блок b_negbal и шлёт CoA Disconnect.
БонусыТекущий счёт бонусных баллов (программа лояльности, AbonentsLoyalty).
Рек. платёжМинимальная сумма для непрерывного обслуживания до конца следующего месяца.
Мин. для разблок.При активном b_negbal — сколько нужно внести сейчас чтобы блок снялся.
Хватит доПрогнозная дата выхода в минус: today + (ostatok / monthly_burn_rate).
Калькулятор «Рассчитать платёж»

Поле «Рассчитать до даты» + кнопка → возвращает требуемую сумму пополнения чтобы баланса хватило до указанной даты. Использует тот же алгоритм что billing_simulate — учитывает тариф, активные UsersUsluga, будущие sched_date, бонусы и скидки.

Где смотреть в коде

История

Лента всех изменений по абоненту, синхронизирована с разделом Аудит. Каждая запись содержит:

Drill-down ссылки ведут на конкретный объект изменения (FinOp, UsersUsluga, ABONENTS_BLOCK).

Тариф и услуги

Текущий тариф абонента + список индивидуальных услуг (UsersUsluga):

Бонусы

Подвид «Услуг», но с фильтром по знаку: компенсации и скидки (summa < 0) или начисления типа «бонус» по имени:

Финансовые операции (вкладка)

Журнал приходов, расходов и сторно по счёту абонента — компактная таблица с быстрыми фильтрами и формами создания операций. Открывается в карточке абонента по табу «Операции»: /admin/Abonents/<id>/operations/.

Вкладка Операции — общий вид

Содержание раздела
KPI-блок

Сразу под навигацией — 4 цветные KPI-карточки:

Клик по KPI «Приход» / «Расход» / «Сторно» работает как chip-фильтр — быстрая фильтрация без открытия toolbar.

Toolbar в одну строку

Все элементы фильтра помещаются в одну линию (на mobile уезжают в bottom-sheet по кнопке «Фильтры»). Слева направо:

  1. Кнопки Приход / Расход — открывают модалку создания.
  2. Chip-фильтры по знаку: «Все · ↓ · ↑ · ↻» (icon-only с tooltip).
  3. Диапазон дат: [13.05.2025] — [13.05.2026] в input-group, без подписей «с/по».
  4. Селектор типа операции (выпадающий список FinTypes).
  5. Поиск по описанию (растягивается на всё свободное место).
  6. Кнопка — применить фильтры.
  7. Кнопка — экспорт CSV выборки.

Дефолтный период: 12 месяцев (изменено в build 762). Раньше показывался только текущий месяц — оператору приходилось расширять вручную. Теперь сразу видна вся годовая история.

Модалка «Приход / Расход»

Одна модалка #addFinOpModal для двух режимов. Открывается из toolbar или из шапки на mobile. Цветной header (зелёный для прихода / красный для расхода) с белым крестиком закрытия.

Поля:

Модалка Приход (зелёный header)

Модалка Расход с раскрытой услугой

Привязка услуги к расходу

Только в режиме «Расход» под основными полями есть ссылка «+ Добавить услугу». Клик — раскрывается селект из 300 услуг каталога (/rest_api/v2/Usluga/?per_page=300&deleted=false). Услуги показаны с суффиксом-ценой для удобства выбора:

Подключение интернета · +1500.00 ₽
Скидка за электроэнергию 50 · -50.00 ₽
бонус 100 руб/мес · -100.00 ₽
Бонус 650р/мес · -650.00 ₽
…

Привязка услуги к финоперации сохраняется в FinanceOperations.usluga_id (FK → Usluga) — для отчётности по типам расходов.

Услуга опциональна — оператор может оформить «голый» расход без привязки.

Mobile (≤768px)

На мобильных:

Вкладка Операции на mobile

Модалка Расход на mobile (fullscreen)

Тёмная тема

Полная адаптация для тёмной темы: KPI-карточки, toolbar, таблица операций, модалка (background #1f2227, поля #2a2f37, borders #3a3f47, labels #c8d0d8).

Вкладка Операции в тёмной теме

Модалка Расход в тёмной теме

Действия со строкой операции
Права доступа и ограничения политики (build 768)

Поведение всех действий с финансовыми операциями (создание, редактирование, удаление) определяется 5 настройками в /admin/settings/system/?tab=security. По умолчанию ничего не ограничено — стандартное поведение Django admin (любой is_staff=True может всё).

НастройкаЧто меняетDefault
RESTRICT_MANUAL_FINOPS_TO_SUPERUSER Кнопки «Приход»/«Расход», редактирование и удаление доступны только is_superuser=True или членам группы root False
ALLOW_USLUGA_IN_MANUAL_FINOPS Показывать ссылку «+ Добавить услугу» в модалке Расхода False
MANUAL_FINOP_MAX_AMOUNT Запрет операций с суммой выше N ₽ (защита от опечаток). 0 = без ограничения 0
MANUAL_FINOP_REQUIRE_DESCR Запрещает пустое поле «Описание» при ручных операциях False
ALLOW_FINOP_DELETE Если выключено — кнопка удаления скрыта, доступно только сторнирование (для бухгалтерской отчётности) True

Все ограничения проверяются и в backend (HTTP 403 на API endpoints finops_ajax, finops_crud_ajax, FinOpChangeView) и в frontend (кнопки и ссылки скрыты в HTML, чтобы не вводить пользователя в заблуждение).

Helpers backend (billing.services.settings_service):

Важно: ограничения касаются только ручных операций через UI. Автоматические процессы (Celery worker «billing_worker» для абонплаты, webhook ЮKassa, обещанный платёж, autoblock и т.п.) работают как обычно — без проверок. Это нужно чтобы политика «только суперюзер» не сломала автоматическое начисление.

Обновление баланса

Создание / редактирование / удаление FinOp автоматически обновляет account.ostatok (build 761 hotfix — ранее create/edit писали запись в БД, но баланс не трогали; только delete делал rollback, что приводило к симметричному багу «удалил → баланс вырос»).

Логика:

При уходе баланса в минус — Celery beat task process_blocks (каждые 5 минут) автоматически создаст AbonentsBlock(b_negbal=True) + CoA-disconnect через _block_negative_balance().

Точки подключения — назначение и роль

Содержание раздела

Точка подключения (CONNECTION_POINTS) — это запись о физическом подключении абонента к сети провайдера: квартира, розетка, порт коммутатора. Хранится в таблице CONNECTION_POINTS. Один абонент может иметь несколько точек (две квартиры, резервная линия, разные приставки).

Зачем нужна точка подключения

Точка решает четыре задачи:

  1. Физическая привязка к инфраструктуре — какая розетка → какой порт свитча → какой VLAN → какой IP-пул. Используется техником для выезда на адрес;
  2. Предотвращение двойного подключения — порт SWITCH_P_ID резервируется. Биллинг не даст подключить двух абонентов к одному порту;
  3. OPT82-авторизация — сопоставление DHCP Option 82 (IP свитча + номер порта) с записью точки. Без точки OPT82 не работает;
  4. Резерв адресов — точку можно создать заранее (порт прокинут в подъезд), без привязки к абоненту. Когда абонент подпишется — она ему присваивается.
Поля и связи
ПолеСвязьОписание
NAMEПроизвольное имя («Кв. 12, розетка 1»)
HOME_IDFK → homesДом
FLAT + SOCKETКвартира + номер розетки
SWITCH_IDFK → switchКоммутатор уровня доступа
SWITCH_P_IDFK → SWITCH_PORTSКонкретный порт коммутатора
IP_PULL_IDFK → ip_pullИз какого пула выдаётся IP клиентам этой точки
ABONENT_IDFK → abonentsКому принадлежит (NULL = резерв)
USER_IDFK → usersКакая учётка работает через эту точку (NULL = резерв)
Когда нужно создавать точку
Что делать с точками без жителей

На testbill 2026-05: 914 точек без USER_ID и ABONENT_ID. Это не мусор:

Важно: при перемещении абонента из одной квартиры в другую правильнее не удалять старую точку, а отвязать (ABONENT_ID=NULL). Тогда историю подключения видно через USERS_USLUGA_HISTORY и аудит, а сама розетка остаётся в каталоге как «резерв» для нового жильца.

Точки подключения и Оборудование

Объединённая вкладка (build 71). Содержит:

Учётные записи (Users) — назначение и роль

Содержание раздела

Учётная запись (Users) — это запись для авторизации в RADIUS на конкретную услугу (Internet, IPTV, VoIP). Это не Django-юзер админки, а отдельная сущность, специфичная для биллинга.

Чем Users отличается от Abonents
СущностьЧто этоКратность
AbonentsДоговор / физ или юр лицо1 на клиента
UsersУчётная запись для одной услугиN на абонента

Пример: Иван Иванов (один абонент) подписан на:

На каждую — отдельная Users-запись с уникальным LOGIN, паролем, MAC и привязкой к NAS. Все три записи через ABONENT_ID ведут на один Abonents-договор.

Поля и связи
ПолеНазначение
LOGIN + PSWЛогин/пароль для PPP/CHAP/PAP-аутентификации
IP / SNATIPНазначенный IP (статика). При DHCP/PPPoE заполняется RADIUS-ом из пула
MACMAC-адрес устройства абонента (для DHCP/MAC-bind)
NAS_IDЧерез какой NAS работает (заполняется при первой авторизации)
PULL_IDИз какого IP-пула выдан адрес
SWITCH_ID + PORTЧерез какой коммутатор и какой порт
AUTH_TYPE_IDТип авторизации (см. ниже)
TARIF_IDОпционально — свой тариф для этой учётки (если NULL — наследует от Abonents.tarif)
ENABLEDВключена/выключена. RADIUS отказывает при enabled=false
LOGGEDСейчас в сессии (обновляется RADIUS Accounting)
Типы авторизации

Справочник auth_types (FK Users.AUTH_TYPE_ID) определяет как RADIUS аутентифицирует абонента:

Жизненный цикл
  1. Создание — вручную через карточку абонента (users_inline.html модалка) или автоматически через Abonents.add_service() при добавлении услуги с флагом create_login=True;
  2. Первая авторизация — RADIUS заполняет NAS_ID, IP, LOGGED=true, выдаёт IP из пула;
  3. Сессия — пишутся записи в RADIUS_SESSIONS; Acct-Interim каждые ~10 мин обновляет статус;
  4. Блокировка — при Abonents.block() все Users этого абонента получают enabled=false + CoA Disconnect (см. Блокировки);
  5. Удаление — мягкое (enabled=false) при удалении абонента, или жёсткое через UI после отсоединения от точек подключения и сессий.
Распространённая ошибка: «не могу установить тариф учётке» — потому что обычно тариф у Abonents, не у Users. Поле Users.TARIF_ID используется только в редких сценариях (когда у разных учёток одного абонента нужны разные скорости). Если у вас стандартная схема — тариф ставится на абонента, и все его Users автоматически работают на этом тарифе.

Учётные записи

Логины абонента (Users — один абонент может иметь несколько учёток для Internet/IPTV/VoIP). На каждую строку — 6 действий (build 374):

Inline-редактирование пароля (build 373): двойной клик на ячейке с паролем → input + кнопки 🎲 generate / ✓ save / ✕ cancel. Enter=save, Escape=cancel.

RADIUS-сессии — назначение и роль

Содержание раздела

RADIUS-сессия — запись о факте подключения абонента к сети: с какого момента, через какой NAS, какой IP, сколько байт прошло, когда закончилась. Хранится в таблицах RADIUS_SESSIONS (история) и USERS_RADIUSAUTH (текущее состояние).

Две таблицы: RADIUS_SESSIONS и USERS_RADIUSAUTH

Дублирование данных сделано сознательно ради производительности:

ТаблицаЧто хранитРазмер
RADIUS_SESSIONS История всех сессий за всю жизнь абонентов. Каждая сессия = одна запись с START_TIME, END_TIME, OCTETS_IN/OUT, END_REASON ~400K-7M записей
USERS_RADIUSAUTH Зеркало текущего состояния. Один user = одна запись с полями LOGGED, текущий IP, последние OCTETS_IN/OUT, RADIUS_UPDATE 1 строка на абонента (~5800)

Зачем две таблицы: виджет «Онлайн сейчас» в дашборде должен мгновенно показать «1234 онлайн». В RADIUS_SESSIONS для этого надо найти строки WHERE END_TIME IS NULL среди миллионов записей. В USERS_RADIUSAUTH — простой SUM(LOGGED) на 5800 строк = 10мс. Аналитика и история — только в RADIUS_SESSIONS.

Жизненный цикл сессии
  1. Acct-Start — NAS отправляет RADIUS Accounting-Request с типом Start. Биллинг создаёт запись в RADIUS_SESSIONS с END_TIME=NULL + обновляет USERS_RADIUSAUTH(LOGGED=1, IP=...);
  2. Acct-Interim-Update — NAS шлёт каждые ~10 минут (по Acct-Interim-Interval). Обновляются OCTETS_IN/OUT, SESSION_TIME, RADIUS_UPDATE — БЕЗ создания новой записи;
  3. Acct-Stop — при отключении абонента NAS шлёт Stop. Биллинг проставляет END_TIME=now(), END_REASON=<причина> + сбрасывает USERS_RADIUSAUTH.LOGGED=0.
Причины завершения (END_REASON)
ReasonЧто значит
User-RequestАбонент сам выключил роутер / отключил интернет
Idle-TimeoutNAS закрыл сессию по неактивности
Session-TimeoutДостигнут лимит сессии (если задан)
Lost-CarrierФизический обрыв линии (нет сигнала на порту)
Disconnect-ACKБиллинг отправил CoA Disconnect (блокировка) — NAS прервал сессию
NAS-RebootNAS перезагрузился, сессия закрыта системой
Manual-reset-by-operatorОператор вручную сбросил зависшую сессию через UI (build 372)
Зомби-сессии и их чистка

Зомби-сессия — это когда NAS перезагрузился без отправки Acct-Stop, и в БД осталась запись с END_TIME=NULL, хотя реально сессии нет.

Async accounting (build 450)

До build 450: при 1000+ одновременных сессий FreeRADIUS получал 4M+ Acct-Update пакетов, биллинг записывал каждый синхронно в БД → дикий рост блокировок и расхождений. После build 450:

  1. FreeRADIUS принимает Acct-Update и сразу возвращает Accounting-Response;
  2. Сам пакет кладётся в Celery-очередь (radius_accounting_async);
  3. Воркеры разбирают очередь, дебаунсят множественные Interim в группу (раз в 10 секунд на user);
  4. Записывают батчем в БД.

Результат: 4M conflicting packets → 0, нагрузка на БД снизилась в 50 раз. Recovery «Stop без Start» — генерируется синтетический Start если пришёл Stop без записи (NAS reboot).

Чистка истории

RADIUS_SESSIONS растёт быстро (10K-100K записей в день). Без чистки таблица за год становится огромной. Авто-чистка:

Build 445 — bigint fix: NAS-Mikrotik после переполнения счётчика мог прислать Acct-Session-Time=4_261_480_885 (~135 лет). Колонка SESSION_TIME integer переполнялась → SQL-ошибка → запись не создавалась → виджет «Онлайн» расходился с реальностью. Фикс: clamp до int4_max в internet.py + миграция SESSION_TIME bigint. Виджет: 989 онлайн → 1001, расхождение 0%.

RADIUS / Монитор сессий

URL: /admin/Abonents/<id>/check/. Полнофункциональный live-инструмент мониторинга RADIUS-сессий абонента. После UX-аудита (Sprint 1+2+3, builds 535–544) Health Score 64 → 94+/100.

Монитор сессий — общий вид: контекст тарифа, toolbar с KPI, таблица сессий, live-индикатор

Карточка контекста тарифа

Сверху страницы — компактный градиентный блок с информацией о тарифе абонента: имя, цена, шейпер из TarifRadiusParams (Mikrotik-Rate-Limit), Acct-Interim-Interval, статистика 7 дней (всего сессий / обрывов / процент). При drop_pct > 20% — иконка ⚠ предупреждения «выше нормы по тарифу».

Toolbar с KPI и переключателем периода

Над таблицей — 5 KPI-чипов:

Переключатель периода: Сутки / Неделя / Месяц / Всё. Клик → fetch GET /admin/Abonents/<ab_id>/sessions/kpi/?period=... → JSON → точечное обновление цифр.

KPI обновляются при выборе периода «Сутки»

Live-индикатор и auto-refresh

Справа в toolbar — индикатор «Авто • N с назад» с пульсирующей зелёной точкой. Каждые 30 секунд JS опрашивает GET /admin/Abonents/<ab_id>/sessions/list/, обновляет статус-бейджи и трафик в строках без перезагрузки страницы. При смене статуса (Online → Stale → Завершена или наоборот) — flash-анимация строки и toast-уведомление «🔌 Сессия 8142e97a завершена: User-Request».

Клик на индикатор — пауза. При переключении на другую вкладку браузера (document.hidden) — авто-пауза, при возврате — мгновенный refresh.

Колонки таблицы (12 на десктопе)
Подсветка строк
Drawer-модалка деталей сессии

Клик на «i» в строке (или Enter на сфокусированной строке) открывает offcanvas-end справа шириной 540px с 4 вкладками:

  1. Обзор — 9 ключевых полей в <dl>: Начало / Завершение / Длительность / MAC / IP / NAS / Port / VLAN / Причина.

    Drawer — Обзор

  2. RADIUS — 20 raw-атрибутов: Acct-Session-Id, User-Name, NAS-IP-Address, Calling-Station-Id, Framed-IP-Address, NAS-Port-Id, Tunnel-Private-Group-Id, Acct-Session-Time, Acct-Input/Output-Octets, Acct-Terminate-Cause, raw timestamps в UTC, CEIL_IN/OUT, RATE_IN/OUT.

    Drawer — RADIUS-атрибуты

  3. Трафик — крупные цифры ↓ загрузка / ↑ выгрузка (в МБ/ГБ + точные байты), средний rate в Kbps, длительность.

    Drawer — Трафик

  4. Действия — CoA Disconnect (disabled для закрытых) + 3 копировать-кнопки (MAC / IP / Session ID) + ссылка «Открыть NAS».

    Drawer — Действия

CoA Disconnect — разрыв сессии оператором

Кнопка в строке с активной сессией (или из drawer-модалки → вкладка «Действия»). Клик открывает confirm-модалку с превью последствий:

Confirm-модалка перед CoA Disconnect

Подтверждение → POST /admin/Abonents/<ab_id>/sessions/coa_disconnect/ → backend запускает disconnect_user.delay(broadcast=True) в Celery (CoA на все NAS абонента) + audit-запись (table=RADIUS_SESSIONS) + toast «CoA Disconnect отправлен на все NAS». Через 8 секунд страница перезагружается чтобы обновить статус сессии на «Завершена».

Mobile-вид (ниже 992px)

Таблица из 12 колонок не помещается на телефоне — вместо неё отдельный layout d-block d-lg-none с карточками:

Mobile card-stack

Каждая сессия — карточка с зелёной полосой слева для Online, бейджем статуса/причины сверху, периодом + аптаймом, и 6 ключевыми полями (Логин / NAS / IP / MAC / Трафик / Порт+VLAN). Touch-targets ≥ 44px (a11y-touch-target-design).

Backend-endpoints
URLМетодНазначение
/admin/Abonents/<ab_id>/sessions/detail/<sid>/GETПолная карточка сессии (24+ поля) для drawer-модалки
/admin/Abonents/<ab_id>/sessions/kpi/?period=...GETАгрегаты за период (24h / 7d / 30d / all)
/admin/Abonents/<ab_id>/sessions/list/GETЛёгкий JSON для auto-refresh каждые 30 сек
/admin/Abonents/<ab_id>/sessions/coa_disconnect/POSTРазорвать активную сессию через CoA

Все endpoint-ы реализованы в billing/views/session_monitor_api.py.

Доступность (a11y) и UX-фиксы
История внедрения по билдам
БилдЧто добавлено
519Колонка MAC, фикс таймзоны MSK
525Авто-скрытие колонки «Логин» если 1 учётка
526Колонка IP (Framed-IP-Address)
527Подсветка Online — салатовый фон + зелёная полоса
528Цветной трафик — ↓ зелёный / ↑ красный
529–530Колонка NAS + клик открывает модалку
535 (Sprint 1)Бейджи причин 5 категорий, колонка «Период», тултипы + OUI vendor + copy-on-click, a11y, mobile card-stack
536 (Sprint 2)Drawer 4 вкладки, toolbar с KPI, контекст тарифа, CoA Disconnect
537 (Sprint 3)Группировка Synthetic↔Real, auto-refresh 30 сек, live-индикатор, flash при смене статуса, toast
540–544Серия фиксов: убран sparkline из таблицы, убраны фиолетовые точки-индикаторы, контраст CoA-кнопки на Online

СОРМ

Паспортные данные и реквизиты для выгрузки в РКН по СОРМ-3:

Реквизиты

Произвольные атрибуты абонента (UserAttributes + AttributeValues). Для юр.лиц при первом открытии вкладки авто-создаются 12 пустых атрибутов с default_person=True; для физлиц — с default_individual=True. Атрибуты можно использовать в шаблонах сообщений и печатных формах.

HelpDesk (FreeScout)

Заявки FreeScout, связанные с абонентом (build 78). Запросы к FreeScout API через billing/services/freescout_billing.py::get_abonent_tickets(). Из карточки можно:

Webhook-события из FreeScout приходят обратно: conversation.agent_reply_created → push, conversation.assigned → Telegram админу, conversation.status_changed(closed) → AuditOperations.

Лояльность

Программы лояльности абонента и их история. Управляется через справочник Программы лояльности:

Сообщения

История уведомлений абонента (MsgStack) по всем каналам:

Аудит

Журнал действий пользователей и системных событий по конкретному абоненту (build 773 — переработка вкладки). Подвыборка из AuditOperations с фильтром по abonent_id + связанным USERS-записям через object_id.

Вкладка Аудит карточки абонента — KPI-карточки сверху, под ними toolbar с фильтрами (даты, поиск, тип, CSV), таблица событий

Композиция

Сверху вниз:

  1. KPI-карточки — кликабельные плитки Всего, Финансовые операции, Услуги, Блокировки, Сообщения, Редактирование абонента, Другое со счётчиками. Клик по плитке = фильтр по этой категории. Карточка Всего в правом верхнем углу содержит i-иконку → ссылка на этот раздел документации (build 777).
  2. Toolbar (одна строка, build 773) — диапазон дат (компактные поля 96px без подписей «с/по», разделитель «—»), поиск по описанию, селект «Все типы», кнопка «Применить», кнопка экспорта CSV. Раньше были кнопки Сегодня/Неделя/Месяц/Год — удалены как избыточные.
  3. Таблица с колонками Дата / ID, Событие, Описание, Исполнитель. Vertical-align top — длинные описания читаются проще. Колонка «Событие» = цветной лейбл + drill-иконка в одну строку (`flex nowrap`).
Период по умолчанию

При первом открытии вкладки сервер ставит диапазон последние 12 месяцев (раньше был 1 месяц, build 773). Для более длинной истории — расширить вручную через datepicker. localStorage сохраняет выбранный текст-поиск и тип фильтра, но НЕ даты (даты могут устареть и ввести в заблуждение). Ключ хранилища: audit_filters_v2.

CSV-экспорт

Зелёная кнопка с иконкой fa-file-csv справа от «Применить» в toolbar. Качается выборка с теми же фильтрами что в UI (даты, поиск, абонент). Endpoint: GET /admin/reports/audit_export/?abonent=<id>&date_from=YYYY-MM-DD&date_to=YYYY-MM-DD&search=.... Формат: UTF-8 BOM, разделитель ;, колонки Дата, Событие, Описание, Абонент ID, Абонент ФИО, Исполнитель. Лимит 50 000 строк.

Mobile-вид

Под KPI-плитками — компактная строка .ab-mobile-search-row: поле поиска + кнопка «Фильтры» (38px высота) в одну строку. KPI и десктопный toolbar скрыты (aa-hide-mobile) — фильтры открываются через offcanvas снизу с категориями, диапазоном дат, кнопками Применить / Сбросить. «Сбросить» закрывает offcanvas и редиректит на ?tab=audit с очисткой всех параметров.

Drill-down к объекту

Если у записи аудита есть table_name + object_id, рядом с лейблом события появляется иконка fa-external-link-alt. Клик открывает в новой вкладке прямую ссылку на изменённый объект — например, при изменении finance_operations переход на конкретную финоперацию, при изменении users_usluga — на услугу.

Категории событий
КатегорияЦветИсточник (table_name)
УслугисинийUSERS_USLUGA
ФиноперациизелёныйFINANCE_OPERATIONS, ARCH_ACCOUNT_STACK
Оплататёмно-зелёныйPAY_LOG, ADMIN_ACCOUNTS_LOG
БлокировкаоранжевыйABONENTS_BLOCK
СтатусыжёлтыйSTATUS, OBJECTS_STATUS
АбонентфиолетовыйABONENTS, ATTRIBUTE_VALUES, LOYALTY
Сообщениятёмно-синийMSG_STACK
СОРМсерыйSORM*
Прочеесветло-серыйостальные таблицы

Дополнительные действия