Карточка клиента
Всё, что открывается в карточке клиента: вкладки, действия оператора, история изменений.
9. Карточка клиента
URL: /admin/Abonents/<id>/. Открывается кликом по строке клиента в любом списке (папка, глобальный поиск, модалка счёта). Это центральная рабочая страница оператора — здесь видны все аспекты клиента и доступны все действия.

Аватарка и соцсети клиента
Карточка показывает аватарку клиента (в шапке профиля, в сайдбаре-drawer, в виджете «Биллинг» тикета Поддержки, в тултипах карты CRM) и иконки-ссылки на профили в VK / Telegram / MAX — цветные брендовые иконки без круга, открываются в новом окне. Данные хранятся в реквизитах клиента (атрибуты «Аватарка VK», «Аватарка TG», «VK ID», «Telegram ID», «MAX ID»), файлы аватарок — в GCS.
![]()
flowchart LR
FS["Внешний источник
(фото, VK/TG id)"] -->|"импорт аватарок
матчинг: договор/email/телефон/
соцсети/тикеты/ФИО (транслит+fuzzy)"| ATTR
VKAPI["VK / Telegram
(id из каналов Поддержки)"] --> ATTR
ATTR["UserAttributes
Аватарка VK/TG · VK/TG/MAX ID"] --> GCS["Аватарки в GCS
(постоянные URL)"]
ATTR --> UI1["Профиль клиента"]
ATTR --> UI2["Сайдбар (drawer)"]
ATTR --> UI3["Тикет Поддержки
виджет Биллинг + панель"]
ATTR --> UI4["Карта CRM (тултип)"]
По VK/TG/MAX ID биллинг узнаёт клиента, когда тот пишет в соцсетях/мессенджерах — тикет сразу связывается с карточкой.
Шапка карточки
Сверху страницы — фиксированная панель с:
- ФИО + № договора — заголовок страницы.
- Баланс в рублях с цветовым кодированием (зелёный ≥0, красный <0).
- Тариф и его стоимость в месяц.
- Статус подключения (Активен / Заблокирован / Выкл / Удалён) — цветной бейдж.
- Кнопки быстрых действий: Заблокировать / Разблокировать (с превью последствий), Удалить (soft-delete + CoA Disconnect), Сменить тариф, Создать заявку (HelpDesk), Отправить сообщение (SMS/Email/Push), Печать договора.
- Установить статус — открывает диалог выбора
Status, автоматически отправляет соответствующий шаблон сообщения (AdminMsg).
Все destructive-действия (Заблокировать / Удалить) защищены Bootstrap-confirm с превью последствий (паттерн).
Индикатор несохранённых изменений
Кнопка «Сохранить» (иконка-дискета в шапке карточки) показывает, есть ли в форме несохранённые правки, а изменённые поля подсвечиваются — оператор сразу видит, что нужно сохранить.
Состояния кнопки «Сохранить»:
- Серая, приглушённая — несохранённых изменений нет, сохранять нечего.

- Зелёная с мягкой пульсацией — в форме есть несохранённые правки. Пульсация привлекает внимание, но не раздражает; уважает системную настройку
prefers-reduced-motion(при ней анимация отключается).
Кнопка всегда физически кликабельна — серый вид это лишь подсказка «нечего сохранять», а не блокировка. Если клиент в этот момент обрабатывается биллингом (фоновая операция), кнопка не пульсирует, а tooltip объясняет, почему сохранение временно недоступно.
Подсветка изменённых полей. Любое поле формы, значение которого отличается от исходного, подсвечивается жёлтой рамкой и фоном (класс .ab-field-changed). Работает и для обычных полей, и для Select2-виджетов; есть отдельный вариант для тёмной темы.

Как это работает. При загрузке страницы JavaScript снимает «снимок» исходных значений всех полей формы #changelist-form (часть полей догружается через AJAX — снимок повторяется через короткие интервалы, чтобы захватить и их). На событиях change/input текущее значение сравнивается со снимком: поле получает/теряет подсветку, кнопка «Сохранить» — зелёный вид. Технические поля (CSRF-токен, management-формы formset'ов) из отслеживания исключены.
Индикатор — это только визуальная подсказка поверх формы. Он не участвует в самом сохранении: клик по кнопке отправляет форму всегда, независимо от состояния индикатора.
Быстрый доступ (под шапкой)
Шесть карточек-сниппетов над аккордеоном на вкладке «Информация» — самые востребованные данные по клиенту без переключения вкладок. Каждая карточка имеет ссылку «→» на соответствующую вкладку для полного просмотра.
| Карточка | Что показывает | Действие «+» |
|---|---|---|
| 📋 Аудит | Последние 8 действий по клиенту (блокировки, смены тарифа, корректировки, входящие SMS) с датой и описанием | — |
| 🎁 Услуги | Назначенные услуги (трафик, скидки) + подсекция «Бонусы» со счётчиком баллов. Эффективная сумма с цветным dot-индикатором enabled/disabled | Добавить услугу / бонус |
| 💰 Операции | Последние финансовые операции с цветным бейджем направления (↓ приход / ↑ расход) и зачёркиванием для сторно | Добавить финоперацию |
| 🎧 HelpDesk | Активные и закрытые тикеты HelpDesk с бейджем статуса и временем последнего обновления | — |
| ✉ Сообщения | Последние SMS/Email/Push/Telegram/VK со статусом доставки (✓ или ⏳) | Отправить сообщение |
| 📡 Сессии | До 5 учётных записей клиента с признаком онлайн/офлайн, IP, временем последнего обновления и трафиком ↓/↑ | — |
Данные загружаются одним AJAX-запросом /admin/Abonents/<id>/quick_cards/ сразу после открытия карточки. Шапка автообновляется раз в 60 секунд (статус Онлайн/Офлайн, баланс) — карточки операторов открыты часами, но всегда актуальны.
Настройки профиля клиента (layout 2-col)
Раздел /admin/settings/system/?tab=abonent_profile (вкладка «Профиль») — 33 глобальные настройки в 9 секциях, управляющие тем, что и как видит оператор на карточке клиента и в списках. Все настройки глобальные (действуют на всех операторов), хранятся в SystemSettings с категорией abonent_profile. Кеш Redis 60 секунд (ctxproc:abonent_profile_v2), сбрасывается при сохранении формы.
С версии 2.1.0 страница в 2-колоночном layout (на экранах ≥1280px). 9 секций укладываются masonry-style через CSS column-count:2 + break-inside:avoid — без выравнивания по высоте, плотно. На узких экранах автоматически 1 колонка.

Также переименованы две соседние вкладки для краткости:
- «Производительность» → «Скорость» (Gunicorn / Celery / БД / Throttling)
- «Сессии и RADIUS» → «RADIUS» (таймауты, retention, NAS auto-assign)


Секция 0 — Быстрый доступ (Quick Cards)
Master-toggle + 6 индивидуальных тогглов по карточкам (Аудит / Услуги / Операции / HelpDesk / Сообщения / Сессии). При выключении master блок не загружается — карточка клиента грузится быстрее (нет AJAX-запроса /quick_cards/).

Секция 1 — Видимость секций карточки
Управление крупными элементами карточки клиента:
- 🚦 Плашка диагностики связи — светофор (по умолчанию вкл.)
- 📊 Sparkline 24h — мини-график активности в развёрнутой плашке (по умолчанию вкл.)
- ⚡ Кнопки авто-действий (Сброс сессии / Перевыдать IP / Тикет монтажнику) (по умолчанию вкл.)
- 🕑 Recent pages в шапке — дропдаун последних посещённых страниц (по умолчанию вкл.)

Секция 2 — Колонки списка клиентов
4 тоггла-«дефолта» для колонок в /admin/Abonents/<folder>/ и глобальном поиске. Действует через def-флаги в COLS-массиве — каждый оператор может переопределить в своей user-config через модалку «Колонки».
- 🏠 Точка подключения — колонка с именем ConnectionPoints
- 📡 Онлайн — иконка-индикатор RADIUS-сессии (доп. JOIN, отключение ускоряет рендер)
- 🤝 Иконка обещанного платежа у баланса
- 📞 Tooltip с контактами на иконках ✉/📞

Секция 3 — AJAX и автообновление
- Интервал автообновления Quick Cards: 0 (выкл) / 30 / 60 (default) / 120 / 300 сек
- Автообновлять вкладки «Услуги»/«Финопы» — для длинных сессий оператора
- TTL Redis-кеша Quick Cards: 0..300 сек (0 = выкл, кеш в БД на каждый запрос). При высокой нагрузке поставить 30-60
- Показывать «Загрузка...» placeholder — spinner до AJAX-ответа

Секция 4 — Дефолты карточки
- Вкладка карточки по умолчанию: Информация / Финансы / Услуги / Точки подключения / Сессии / HelpDesk
- Раскрытая секция accordion: Финансовая информация / Контакты / СОРМ / Доп. настройки
- Сортировка операций: новые сверху / старые сверху
- Период «Операции» по умолчанию: Месяц / Квартал / Год / Все. Применяется через JS+sessionStorage, не нарушая контракт «всё видно по умолчанию».
- Период «Сообщения» по умолчанию: Неделя / Месяц / Квартал / Все

Секция 4А — Управление вкладками карточки
Управление полным набором из 13 вкладок карточки клиента: видимость (показать/скрыть), порядок (drag&drop) и стиль отображения.
Возврат скрытых вкладок: «Задачи» (CRM_TASK, скрыта) и «Оборудование и подключения» — теперь можно вернуть в навбар одним кликом.
4 стиля отображения навигации вкладок (превью видно прямо в форме):
- Классический (default) — AdminLTE nav-tabs, как сейчас
- Pills (таблетки) — скруглённые кнопки
- Подчёркивание — Material-style underline
- Компактный — узкие табы, на mobile только иконки
Drag&drop: каждую вкладку можно перетащить за иконку «☰» в любую позицию. Изменения сохраняются в ABONENT_PROFILE_TABS_CONFIG как JSON. Кнопка «Сбросить к дефолту» возвращает порядок и видимость из build_abonent_card_tabs в коде.

Секция 5 — Стиль и плотность
- 📏 Compact mode карточки — уменьшает padding/font-size в табах и аккордеоне
- 👤 Показывать аватары/инициалы клиента
- 🎨 Цвет бейджа «Активен»: Зелёный / Бирюзовый / Серый

Секция 7 — Баннеры уведомлений оператору
5 баннеров-подсказок в шапке карточки клиента, каждый показывается только при выполнении условия:
- 🤝 Активный обещанный платёж — есть
promise_date_end(по умолчанию вкл.) - ⚠ Низкий баланс — баланс <
LOW_BALANCE_NOTIFY_THRESHOLD_RUB(по умолчанию вкл.) - 📅 Скоро автосписание — за 1-3 дня до
BILLING_CHARGES_HOUR(по умолчанию выкл.) - 🔇 Сейчас тихий час — текущее время в окне
NOTIFY_QUIET_HOURS_*(по умолчанию выкл.) - 📞 Свежее обращение в HelpDesk — есть тикет < 24ч (по умолчанию вкл.)

Секция 8 — Прочее
- #ID Показывать ID клиента в шапке — добавляет «(ID:1234)» рядом с номером договора. Полезно при работе с API/SQL.
- 🛡 Маскировать паспорт от не-superuser — серия/номер в СОРМ-блоке показываются как «••••»/«••••••» для обычных операторов (PCI/152-ФЗ).
- 📱 Скрывать пароль RADIUS на mobile — чип с паролем не показывается на экранах ≤768px (риск утечки выше).

Технические детали
- Хранение:
SystemSettingsс категориейabonent_profile. 32 boolean/choice ключа + 1 JSON-ключ (ABONENT_PROFILE_TABS_CONFIG) +ABONENT_PROFILE_TABS_STYLE(choice). - Передача в шаблоны: context_processor
billing.context_processors.smit_versionотдаётSMIT_PROFILE_PREFSdict. Кешируется через Redis 60с в ключеctxproc:abonent_profile_v2. - Сброс кеша: автоматически при сохранении формы (
cache.delete()в POST handler). - Без миграций БД — используется готовая инфраструктура
settings_service.set_setting()/get_setting(). - Backward compat: все дефолты подобраны так, что поведение до фичи сохраняется при пустой БД настроек.
i-иконка рядом с заголовком «Быстрый доступ» на карточке клиента ведёт прямо в этот раздел документации.
Диагностика связи — плашка-светофор
В шапке карточки клиента — автоматический экспресс-диагноз состояния связи. Светофор из 3 цветов (🟢 норма / 🟡 возможны проблемы / 🔴 проблема) с раскрывающимися деталями и готовым ответом для клиента. Все данные читаются из БД read-only (никаких записей, ≤6 SQL-запросов).
Цель функционала — сократить время разбора жалобы «не работает интернет» с 5 минут до 10 секунд.
Что показывает плашка
Свёрнутая плашка — компактный светофор + 5 чипов проверок. Каждый чип имеет свой цвет (зелёный/жёлтый/красный/серый) и tooltip с детальным summary.

5 проверок диагностики
| Чип | Что проверяет | Когда красный |
|---|---|---|
| 💰 Биллинг | enabled, deleted, баланс, активные блоки, тариф, ОП | удалён / отключён / нет тарифа / b_admin / b_negbal |
| 📶 Сессия | Users.enabled/logged, активная RADIUS-сессия, свежесть UPDATE_TIME | LOGGED=true без сессии (зависшая) |
| 📜 Разрывы | END_REASON последних 10 сессий, Lost-Carrier за час/сутки | 2+ Lost-Carrier за час → выезд монтажника |
| 📊 Трафик | IN_OCT/OUT_OCT текущей активной сессии | (всегда green/yellow/grey) |
| 📍 Адрес | Abonents.HOME_ID/FLAT == CONNECTION_POINTS.HOME_ID/FLAT | адрес и точка подключения не совпадают |
Общий уровень (overall) = худший из всех (red > yellow > green > grey).
Развёрнутый вид и готовый ответ
Клик на кнопку «Подробнее» раскрывает 5 карточек с полным summary каждой проверки + блок «Готовый ответ для клиента» с кнопкой «Скопировать». Текст ответа автоматически подбирается по decision tree из 12 веток (нет тарифа → инструкция назначить тариф, 2+ Lost-Carrier → инструкция про кабель и роутер, активный трафик → «перезагрузите Wi-Fi», блок за неуплату → «внесите оплату», и т.д.).

Sparkline 24ч и auto-actions
Sparkline «Сессии за 24 часа» — мини-график из 24 столбиков, каждый = 1 час. Зелёный = час с активной сессией, серый = тишина, красный = час с Lost-Carrier. Tooltip на каждом столбике показывает состояние.
Под графиком — 3 кнопки-действия:
- ⚡ Сброс сессии — закрывает зависшие сессии в RADIUS_SESSIONS, сбрасывает
Users.LOGGED+USERS_RADIUSAUTH.LOGGED, шлёт CoA Disconnect broadcast на все enabled NAS. Через 1 секунду диагноз автоматически обновляется. - 🔄 Перевыдать IP — освобождает текущий IP и выделяет новый из пула NAS (main → nat → white → hotspot) через
allocate_ip_from_poolsс защитой от race condition. Сразу же шлёт CoA, чтобы клиент переавторизовался. - 🔧 Тикет монтажнику — создаёт тикет в HelpDesk с автозаполнением: ФИО / договор / телефон / адрес + результат диагноза + 🚨 подсветка Lost-Carrier как обоснование выезда. Тикет создаётся под аккаунтом оператора, видится в карточке вкладкой «Поддержка».
Каждое действие защищено JS-confirm, показывает spinner во время выполнения, выводит toast «успех/ошибка», пишет запись в аудит.

UX-особенности
- Точечный refresh кнопкой «Проверить сейчас» — fetch
/admin/Abonents/<id>/diagnose/+ обновление DOM без перезагрузки страницы. Прокрутка и открытые вкладки не теряются. - Skeleton-loader — пока ждём ответ, чипы становятся полупрозрачными и по ним идёт shimmer-анимация, иконка светофора крутится. Анимация GPU-композитная (через
transform: translateX), уважаетprefers-reduced-motion. - Toast-уведомления — copy / refresh / результаты действий показывают всплывающее уведомление 2.2с в правом нижнем углу с цветной иконкой success/error/info.
- Защита от падения — каждая из 5 проверок обёрнута в
_safe_check(). Если одна из них падает (например, повреждена запись в БД) — она возвращает grey-результат «Ошибка проверки», остальные продолжают работать. - Toggle через
VpnConst CONNECTIVITY_DIAG_ENABLED(defaultTrue). При отключении плашка не рендерится, остальная карточка не затронута.
REST API и CLI
Для интеграции с внешними системами:
- REST API:
GET /rest_api/v2/diagnose/<login>/→ JSON DiagnoseReport. Session-auth Django. Используется в HelpDesk-модуле, Telegram-боте, custom-скриптах. - CLI команда:
python manage.py diag <login>(или--contract,--abonent-id) — текстовый отчёт с цветовой подсветкой. Опция--jsonдля скриптов,--telegramотправляет компактную сводку в админ-чат алертов.
Пример CLI:
$ docker exec app-web-1 python manage.py diag 5927
═══ Диагноз: 🟡 YELLOW — #5927 Кельн Валерий Валерьевич ═══
[GREEN] billing — Активен, баланс 6.01 ₽, 2025_Для Тебя: 749.0 руб./мес.
[GREEN] session — Сессия не активна (клиент офлайн)
[YELLOW] end_reasons — Lost-Carrier ×3 за сутки — нестабильная линия
[GREY] traffic — Активной сессии нет
[GREEN] address — Адрес клиента совпадает с точкой подключения
📋 Готовый ответ для клиента:
Здравствуйте! Проверим вашу линию и свяжемся с вами в течение 30 минут.
Реализация (для разработчиков)
- Service-layer:
billing/services/connectivity.py— 5 функцийcheck_*()+connectivity_diagnose()+build_advice(). Read-only, ≤6 SQL. Покрыто 37 unit-тестами вbilling/tests/test_connectivity.py. - Endpoint:
billing/views/diagnose.py—diagnose_view(GET),diag_action_view(POST для 3 actions),diagnose_rest_api(REST API). - UI:
billing/templates/abonents_form/abonent_post_status.html— плашка-баннер + раскрывающиеся детали + sparkline + auto-actions + JS-обработчики + ~400 строк CSS. - Интеграция с карточкой:
BlockStatusViewвbilling/views/abonents.py— передаётdiagчерез try/except в контекст шаблона.
Вкладки
Вкладки с подробной информацией. Часть из них объединена в группы: «Учётные записи + RADIUS», «Точки подключения + Оборудование» — соседние вкладки навигируются стрелками. Некоторые вкладки условные: «Банк» (иконка fa-university 🏛) появляется, когда у клиента есть платежи по банковским выпискам.
Информация
Основные поля клиента в двух колонках. После рестайлинга карточки вкладка перестроена под единый визуальный язык: hero-блоки с фирменной зелёной полосой слева, единый размер бейджей, общие классы для модалок и инпутов.
Слева — основной блок:
- Тариф — отдельный hero-блок
.ab-hero-tarifс крупной иконкой-плиткой, названием тарифа, ценой/мес и кнопкой «Изменить тариф». При клике открывается модалка смены тарифа с поиском, фильтрами «Физлица / Юр.лица», подсветкой текущего и тарифов с нулевой ценой. - Лояльность — поле
loyaltyпоказывается только если у клиента действительно есть программа лояльности. - Комментарии операторов — hero-секция с зелёной полосой, счётчиком, кнопкой «+ Добавить». Каждый комментарий идёт строкой с
#id · DD.MM.YYYYи кнопкой удаления; список прокручивается до 180px.
Справа — блок «Договор и контакты» (объединение бывших «Контактная информация» и «Договор»). Содержит:
- Номер договора, Дата договора, Адрес (Select2 с автокомплитом DaData), Подъезд / квартира, Email, Телефон, чекбокс «Юр. лицо».
- Юр-поля «Отправлять счёт», «Формировать акт автоматически», «Дата выставления акта» — показываются только при включённом чекбоксе «Юр. лицо» (динамически JS, без перезагрузки).
- Незаполненные обязательные поля подсвечиваются красной рамкой (
.smit-empty) — оператор сразу видит, что нужно дозаполнить. Подъезд и квартира не подсвечиваются (необязательные для частных домов).
Чекбокс «Юр. лицо» и валидация тарифа
При включении чекбокса «Юр. лицо» на физлице с физическим тарифом (Tarif.is_business=false) JavaScript показывает confirm-диалог и при подтверждении открывает модалку смены тарифа с автоматическим переключением на фильтр «Юр.лица». Серверная валидация (Abonents.clean()) дополнительно проверяет: если company=true и текущий тариф не Business — ValidationError на поле tarif с понятным текстом.
Маски и валидация email/телефон
Оба контактных поля имеют клиентскую и серверную валидацию:
- Телефон (
sms): jQuery MaskedInput с маской+7 (999) 999-99-99. Авто-нормализация существующего значения при загрузке (79175551234/89175551234/+7..→+7 (917) 555-12-34). На сервере: извлечение цифр, автоприведение8XXX→7XXXи 10-значного→7+10, проверка длины 11 + префикс 7, сохранение в каноне+7XXXXXXXXXX. - Email:
type=email+inputmode=email+ EMAIL-regex проверка на blur (.is-invalid/.is-validподсветка). Поддержка нескольких через,/;(legacy). Серверная валидация —django.core.validators.EmailValidatorпо каждому адресу.
Все ошибки серверной валидации собираются в один ValidationError(dict), чтобы оператор видел все проблемы сразу.
Блок «Смена тарифа / Блокировки»
Внутри блока JavaScript добавляет три визуальных подзаголовка-разделителя, чтобы поля даты не шли плоской простынёй:
- 🔄 Запланированная смена тарифа —
tarif_next,tarif_next_date,tarif_next_date_prev(янтарь). - ⏸ Добровольная блокировка —
own_disabled_start,own_disabled_end(бирюза). - 🚫 Административная блокировка —
disabled_date,disabled_date_end(красный).
Что убрано/скрыто на вкладке
По запросу администрации со вкладки «Информация» убраны:
- ФИО-заголовок в самой форме — дублировал шапку карточки. Скрытый input остался для валидации/сохранения.
- Поле «Точка подключения» — было JS-вставкой, дублировало отдельную вкладку «Точки подключения».
- Поле «Группа» (parent) — техническое, дублировало папку-родителя.
- Поле «Скидка %» — рекомендуется использовать программы лояльности.
- Блок «Особые настройки» (subgroup 60) — 17 технических полей (CATEGORY, OPERATOR, ACCOUNT, USLUGA_RANGE_TYPE, SYNC1C, ALERT_*, AGENT_SCHEME и др.), которые операторам не нужны. Перенесены в отдельные подразделы при необходимости.
- Пустой блок «Договор» — все поля собраны в «Договор и контакты», субгруппа 26 содержит только
CONTRACT_PREFIX(только для папок). - Блок «Аккаунты / Скорость» с вкладки «Информация» — дублировал отдельную вкладку «Аккаунты», карточка теперь чище.
Ревизия бейджей шапки
В нижнем ряду бейджей (.abon-summary-chips) убраны дубли:
- Тариф — теперь только в шапке и в hero-блоке, в чипах не повторяется.
- Баланс — только в шапке справа (красный/зелёный), в чипах не повторяется.
- Логин учётки — показывается только если он отличается от номера договора (типичный случай — совпадает, тогда чип скрыт).
- «Учётка вкл/выкл» и «Онлайн/Офлайн» — показываются только для НЕ-заблокированных клиентов (для заблокированного эти статусы избыточны).
Все чипы приведены к единому языку: единая высота 24px, полный pill (radius 999px), solid пастельный фон без градиентов, тонкая граница того же оттенка. Hover — лёгкое затемнение через filter: brightness(.96).
Плашка диагностики связи и заблокированные
Для заблокированных и отключённых клиентов плашка-светофор «Проблема со связью» не показывается — у них нет «проблем», есть ожидаемое состояние блокировки. Плашка появляется только для активных без записей в AbonentsBlock.
Mini-блок СОРМ под контактами показывает паспорт + ИНН с кнопкой быстрого редактирования (без перехода на отдельную вкладку). Для юр.лиц — ИНН/КПП/ОГРН + В лице/Директор.
Блок «Финансовая информация»
Под основной информацией клиента отдельная карточка с расчётами по балансу. Состоит из двух частей: таблица балансов и параметры порогов/прогнозов.
Таблица балансов
Три варианта расчёта с разделением списаний на предоплату и постоплату:
| Строка | Формула | Что значит |
|---|---|---|
| Бух. | admin_accounts.ostatok / 10¹⁰ | Бухгалтерский баланс — фактическое значение лицевого счёта. |
| Текущий | Бух − Расход предоплата + Приход | С учётом обещанных платежей и предстоящих списаний за уже оказанные услуги. |
| На конец месяца | Текущий − Расход постоплата | Прогноз остатка после всех списаний до конца расчётного периода. |
Колонки:
- Приход — сумма активного обещанного платежа (
Usluga 30353). - Расход предоплата — будущие списания услуг с
payment_type=prepaidв этом месяце. - Расход постоплата — услуги по факту использования (трафик, услуги-пакеты).
Параметры и прогнозы
| Поле | Назначение |
|---|---|
| Порог предупр. | Если ostatok < threshold — отправляется уведомление через MsgStack (Email/SMS/Push). |
| Порог откл. | Если ostatok < threshold — billing_worker ставит блок b_negbal и шлёт CoA Disconnect. |
| Бонусы | Текущий счёт бонусных баллов (программа лояльности, AbonentsLoyalty). |
| Рек. платёж | Минимальная сумма для непрерывного обслуживания до конца следующего месяца. |
| Мин. для разблок. | При активном b_negbal — сколько нужно внести сейчас чтобы блок снялся. |
| Хватит до | Прогнозная дата выхода в минус: today + (ostatok / monthly_burn_rate). |
Калькулятор «Рассчитать платёж»
Поле «Рассчитать до даты» + кнопка → возвращает требуемую сумму пополнения чтобы баланса хватило до указанной даты. Использует тот же алгоритм что billing_simulate — учитывает тариф, активные UsersUsluga, будущие sched_date, бонусы и скидки.
Где смотреть в коде
- View:
billing/views/abonents.py::add_finance_tab+ контекст дляshow_fields.html - Шаблон:
billing/templates/abonents_form/balanse_table.html - Расчёт «Хватит до»:
billing/services/balance_forecast.py::days_until_negative
История
Лента всех изменений по клиенту, синхронизирована с разделом Аудит. Каждая запись содержит:
- Дату и время.
- Тип изменения (создание / редактирование / удаление / блокировка / финопер).
- Оператора (кто внёс изменение).
- Описание + diff (какие поля изменились, было → стало).
Drill-down ссылки ведут на конкретный объект изменения (FinOp, UsersUsluga, ABONENTS_BLOCK).
Тариф и услуги
Текущий тариф клиента + список индивидуальных услуг (UsersUsluga):
- Изменение тарифа — Select2 с предпросмотром стоимости. Можно задать «Тариф следующий» (
tarif_next) для отложенной смены с начала следующего расчётного периода. - Список услуг с фильтрами enabled / disabled / deleted. Каскад отображения суммы:
UU.summ → Usluga.summa → Usluga.price; источник указывается явно в плейсхолдере. - Модалка редактирования: Услуга (Select2 AJAX), Индивидуальная сумма, Скидка, Кол-во, Дата начала, Дата следующего списания, Примечание, Включена.
- Опция «Очистить сумму» возвращает каскадное вычисление (
summ=NULL).
Бонусы
Подвид «Услуг», но с фильтром по знаку: компенсации и скидки (summa < 0) или начисления типа «бонус» по имени:
- Серый бейдж «с DD.MM.YYYY» для отложенных бонусов (
enable_date > now). - Редактирование
enable_dateв модалке — datetime-local с кнопкой очистки. - Пример: «Ежемесячный бонус» (Usluga #30049) с индивидуальной суммой −500 ₽ — компенсация за акцию.
Финансовые операции (вкладка)
Журнал приходов, расходов и сторно по счёту клиента — компактная таблица
с быстрыми фильтрами и формами создания операций. Открывается в карточке клиента
по табу «Операции»: /admin/Abonents/<id>/operations/.

Содержание раздела
KPI-блок
Сразу под навигацией — 4 цветные KPI-карточки:
- Всего — общее число операций за выбранный период. В подписи сразу указан период: «N операций · 13.05.2025 — 13.05.2026».
- Приход — сумма всех зачислений (зелёный, иконка
↓). - Расход — сумма всех списаний (красный, иконка
↑). - Нетто — приход минус расход. Цвет зависит от знака.
Клик по KPI «Приход» / «Расход» / «Сторно» работает как chip-фильтр — быстрая фильтрация без открытия toolbar.
Toolbar в одну строку
Все элементы фильтра помещаются в одну линию (на mobile уезжают в bottom-sheet по кнопке «Фильтры»). Слева направо:
- Кнопки Приход / Расход — открывают модалку создания.
- Chip-фильтры по знаку: «Все · ↓ · ↑ · ↻» (icon-only с tooltip).
- Диапазон дат:
[13.05.2025] — [13.05.2026]в input-group, без подписей «с/по». - Селектор типа операции (выпадающий список FinTypes).
- Поиск по описанию (растягивается на всё свободное место).
- Кнопка — применить фильтры.
- Кнопка — экспорт CSV выборки.
Период по умолчанию: 12 месяцев — сразу видна вся годовая история, при необходимости диапазон сужается вручную.
Модалка «Приход / Расход»
Одна модалка #addFinOpModal для двух режимов. Открывается из
toolbar или из шапки на mobile. Цветной header (зелёный
для прихода / красный для расхода) с белым
крестиком закрытия.
Поля:
- Тип операции — селект, по умолчанию выбрано:
- «Ручная проводка» (sign=+1) для прихода
- «Корректирующее списание» (sign=−1) для расхода
- Сумма (руб.) — в рублях, обязательное.
- Баланс — текущий баланс клиента (read-only справа).
- Описание — произвольный текст (по умолчанию «Ручная проводка» / «Расход»).
- Услуга — опционально, см. ниже.


Привязка услуги к расходу
Только в режиме «Расход» под основными полями есть ссылка
«+ Добавить услугу». Клик — раскрывается селект из 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)
На мобильных:
- Toolbar скрыт; вверху видны только 3 кнопки в шапке: Приход · Расход · Фильтры.
- Кнопка «Фильтры» открывает bottom-sheet со всеми фильтрами (даты, тип, поиск, chip-знаки) как на других mobile-страницах.
- Модалка операции —
modal-fullscreen-sm-down(на весь экран). - Поля input
font-size: 16px(iOS Safari не зумит при фокусе).


Тёмная тема
Полная адаптация для тёмной темы: KPI-карточки, toolbar, таблица операций,
модалка (background #1f2227, поля #2a2f37, borders #3a3f47,
labels #c8d0d8).


Действия со строкой операции
- Цветовое кодирование строк: зелёный фон — приход, красный — расход, зачёркнутые — сторнированные.
- Клик по строке — открывает модалку редактирования (
#editFinOpModal) с расширенным набором полей: тип, сумма, описание, дата, период, владелец, услуга, кредит-тип, связанная операция. - Удаление операции — иконка корзины. При удалении автоматически
откатывается баланс счёта (
ostatok -= op_summa) — см. DeleteObjectFormMixin._rollback_balance_on_delete. - Сторнирование — создаёт парную обратную операцию с пометкой
storno=True; обе строки отображаются зачёркнутыми. - Печать чека — для операций оплат генерируется PDF (получение
через
get_pdfview).
Права доступа и ограничения политики
Поведение всех действий с финансовыми операциями (создание, редактирование,
удаление) определяется 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):
can_user_manage_finops(user)— может ли пользователь создавать/редактировать/удалятьis_usluga_in_manual_finops_allowed()get_manual_finop_max_amount()is_manual_finop_descr_required()is_finop_delete_allowed()
Важно: ограничения касаются только ручных операций через UI. Автоматические процессы (Celery worker «billing_worker» для абонплаты, webhook ЮKassa, обещанный платёж, autoblock и т.п.) работают как обычно — без проверок. Это нужно чтобы политика «только суперюзер» не сломала автоматическое начисление.
Обновление баланса
Создание / редактирование / удаление FinOp автоматически обновляет
account.ostatok ( hotfix — ранее create/edit
писали запись в БД, но баланс не трогали; только delete делал rollback,
что приводило к симметричному багу «удалил → баланс вырос»).
Логика:
- Create:
ostatok += op_summa(положительная — приход, отрицательная — расход). - Edit:
ostatok += (новая_сумма − старая_сумма)— дельта. - Delete:
ostatok -= op_summa— откат вклада операции.
При уходе баланса в минус — Celery beat task process_blocks
(каждые 5 минут) автоматически создаст AbonentsBlock(b_negbal=True)
+ CoA-disconnect через _block_negative_balance().
Точки подключения — назначение и роль
Содержание раздела
Точка подключения (CONNECTION_POINTS) — это запись о физическом подключении клиента к сети провайдера: квартира, розетка, порт коммутатора. Хранится в таблице CONNECTION_POINTS. Один клиент может иметь несколько точек (две квартиры, резервная линия, разные приставки).
Зачем нужна точка подключения
Точка решает четыре задачи:
- Физическая привязка к инфраструктуре — какая розетка → какой порт свитча → какой VLAN → какой IP-пул. Используется техником для выезда на адрес;
- Предотвращение двойного подключения — порт
SWITCH_P_IDрезервируется. Биллинг не даст подключить двух клиентов к одному порту; - OPT82-авторизация — сопоставление DHCP Option 82 (IP свитча + номер порта) с записью точки. Без точки OPT82 не работает;
- Резерв адресов — точку можно создать заранее (порт прокинут в подъезд), без привязки к клиенту. Когда клиент подпишется — она ему присваивается.
Поля и связи
| Поле | Связь | Описание |
|---|---|---|
NAME | — | Произвольное имя («Кв. 12, розетка 1») |
HOME_ID | FK → homes | Дом |
FLAT + SOCKET | — | Квартира + номер розетки |
SWITCH_ID | FK → switch | Коммутатор уровня доступа |
SWITCH_P_ID | FK → SWITCH_PORTS | Конкретный порт коммутатора |
IP_PULL_ID | FK → ip_pull | Из какого пула выдаётся IP клиентам этой точки |
ABONENT_ID | FK → abonents | Кому принадлежит (NULL = резерв) |
USER_ID | FK → users | Какая учётка работает через эту точку (NULL = резерв) |
Когда нужно создавать точку
- Подключение нового клиента — точка создаётся вручную в карточке клиента или мастером
AbonentWizardCPView; - Заведение резерва на новый дом — техник прокинул кабель в подъезд → создаёт точки заранее без
ABONENT_ID. Когда жилец подключится, точка цепляется к клиенту; - Переезд клиента — старая точка отвязывается (
ABONENT_ID=NULL), новая создаётся / привязывается. История подключений сохраняется через лог; - Резервная линия — точка создаётся как вторая для того же клиента, обычно для отказоустойчивости в офисах.
Что делать с точками без жителей
На рабочий сервер 2026-05: 914 точек без USER_ID и ABONENT_ID. Это не мусор:
- Если у точки заполнен
SWITCH_IDилиSWITCH_P_IDилиHOME_ID— это резерв: оборудование готово к подключению. Не удалять; - Только полностью пустые точки (без свитча, без адреса, без сокета) — это рудимент от старых тестов. Их можно удалять через UI кнопкой 🗑;
- FK защита:
SWITCH_PORTSиusersссылаются на точку сNO ACTION. UI заблокирует удаление если порт реально занят учёткой.
ABONENT_ID=NULL). Тогда историю подключения видно через USERS_USLUGA_HISTORY и аудит, а сама розетка остаётся в каталоге как «резерв» для нового жильца.
Точки подключения и Оборудование
Объединённая вкладка. Содержит:
- Точка подключения (
ConnectionPoints) — какой коммутатор / порт / VLAN, MAC-адрес. - Оборудование клиента (роутер, ONT, модем) с MAC-адресом и SN.
- Возможность переезда на другую точку через мастер (
AbonentWizardCPView).
Учётные записи (Users) — назначение и роль
Содержание раздела
Учётная запись (Users) — это запись для авторизации в RADIUS на конкретную услугу (Internet, IPTV, VoIP). Это не Django-юзер админки, а отдельная сущность, специфичная для биллинга.
Чем Users отличается от Abonents
| Сущность | Что это | Кратность |
|---|---|---|
Abonents | Договор / физ или юр лицо | 1 на клиента |
Users | Учётная запись для одной услуги | N на клиента |
Пример: Иван Иванов (один клиент) подписан на:
ivan_pppoe— PPPoE для домашнего интернета;ivan_iptv— IPTV-приставка;ivan_office_pppoe— резервный канал в офис.
На каждую — отдельная Users-запись с уникальным LOGIN, паролем, MAC и привязкой к NAS. Все три записи через ABONENT_ID ведут на один Abonents-договор.
Поля и связи
| Поле | Назначение |
|---|---|
LOGIN + PSW | Логин/пароль для PPP/CHAP/PAP-аутентификации |
IP / SNATIP | Назначенный IP (статика). При DHCP/PPPoE заполняется RADIUS-ом из пула |
MAC | MAC-адрес устройства клиента (для 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 аутентифицирует клиента:
- PAP/CHAP/MS-CHAP — классический логин/пароль (PPPoE);
- OPT82 — DHCP Option 82 (по физическому порту коммутатора, без логина). Требует заполненного
SWITCH_PORTSиSwitchTypeс шаблонами парсинга; - MAC — авторизация по MAC-адресу устройства (для устройств без UI ввода логина);
- NONE / Static — статически прописан IP, без авторизации (например, выделенные сервисы юр.лиц).
Жизненный цикл
- Создание — вручную через карточку клиента (
users_inline.htmlмодалка) или автоматически черезAbonents.add_service()при добавлении услуги с флагомcreate_login=True; - Первая авторизация — RADIUS заполняет
NAS_ID,IP,LOGGED=true, выдаёт IP из пула; - Сессия — пишутся записи в
RADIUS_SESSIONS; Acct-Interim каждые ~10 мин обновляет статус; - Блокировка — при
Abonents.block()все Users этого клиента получаютenabled=false+ CoA Disconnect (см. Блокировки); - Удаление — мягкое (
enabled=false) при удалении клиента, или жёсткое через UI после отсоединения от точек подключения и сессий.
Users.TARIF_ID используется только в редких сценариях (когда у разных учёток одного клиента нужны разные скорости). Если у вас стандартная схема — тариф ставится на клиента, и все его Users автоматически работают на этом тарифе.
Учётные записи
Логины клиента (Users — один клиент может иметь несколько учёток для Internet/IPTV/VoIP). На каждую строку — 6 действий:
- ✏ Редактировать — модалка с 4 вкладками: Основные (логин, пароль с генератором, MAC, вкл./комментарий), Сеть (IP-пул, IPv4 с random из пула, NAS, Switch, PORT, VLAN), Адрес (Дом Select2 AJAX, кв.), RADIUS (последние 20 сессий read-only).
- 🔌 Отсоединить (CoA Disconnect) — параллельно на все enabled NAS.
- 🔄 Сбросить зависшую сессию — UPDATE LOGGED=0 + RADIUS_SESSIONS.END_TIME = now.
- 🧪 Тест авторизации — radtest через pyrad напрямую к freeradius:1812. Модалка с цветовым результатом (Accept/Reject/Timeout) + таблица атрибутов RADIUS.
- 📋 Подробнее — drawer-панель справа с шапкой, последними 5 сессиями, графиком трафика за 30 дней, историей паролей, сетевыми привязками.
- 🗑 Удалить.
Inline-редактирование пароля: двойной клик на ячейке с паролем → 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.
Жизненный цикл сессии
- Acct-Start — NAS отправляет RADIUS Accounting-Request с типом Start. Биллинг создаёт запись в
RADIUS_SESSIONSсEND_TIME=NULL+ обновляетUSERS_RADIUSAUTH(LOGGED=1, IP=...); - Acct-Interim-Update — NAS шлёт каждые ~10 минут (по
Acct-Interim-Interval). ОбновляютсяOCTETS_IN/OUT,SESSION_TIME,RADIUS_UPDATE— БЕЗ создания новой записи; - Acct-Stop — при отключении клиента NAS шлёт Stop. Биллинг проставляет
END_TIME=now(),END_REASON=<причина>+ сбрасываетUSERS_RADIUSAUTH.LOGGED=0.
Причины завершения (END_REASON)
| Reason | Что значит |
|---|---|
User-Request | Клиент сам выключил роутер / отключил интернет |
Idle-Timeout | NAS закрыл сессию по неактивности |
Session-Timeout | Достигнут лимит сессии (если задан) |
Lost-Carrier | Физический обрыв линии (нет сигнала на порту) |
Disconnect-ACK | Биллинг отправил CoA Disconnect (блокировка) — NAS прервал сессию |
NAS-Reboot | NAS перезагрузился, сессия закрыта системой |
Manual-reset-by-operator | Оператор вручную сбросил зависшую сессию через UI |
Зомби-сессии и их чистка
Зомби-сессия — это когда NAS перезагрузился без отправки Acct-Stop, и в БД осталась запись с END_TIME=NULL, хотя реально сессии нет.
- Симптом:
Users.LOGGED=1у клиента, который реально оффлайн. Виджет «Онлайн» показывает завышенное число; - Причина: NAS не успел отправить Stop при выключении / NAT crash / power loss;
- Решение — Celery beat
cleanup_stale_sessions(раз в час, + UTC fix):- Находит
USERS_RADIUSAUTH WHERE LOGGED=1 AND RADIUS_UPDATE < now() - 60min(нет Interim больше часа); - Закрывает соответствующие
RADIUS_SESSIONSсEND_REASON='Stale'; - Сбрасывает
USERS_RADIUSAUTH.LOGGED=0.
- Находит
- session_audit_hourly — каждый час сравнивает live-сессии на NAS (через SSH/RPC) с биллингом, шлёт Telegram-алерт если расхождение > 5%.
Async accounting
Учёт сессий идёт асинхронно, чтобы поток пакетов не упирался в запись в БД:
- FreeRADIUS принимает Acct-Update и сразу возвращает Accounting-Response;
- Сам пакет кладётся в Celery-очередь (
radius_accounting_async); - Воркеры разбирают очередь, дебаунсят множественные Interim в группу (раз в 10 секунд на user);
- Записывают батчем в БД.
Результат: 4M conflicting packets → 0, нагрузка на БД снизилась в 50 раз. Recovery «Stop без Start» — генерируется синтетический Start если пришёл Stop без записи (NAS reboot).
Чистка истории
RADIUS_SESSIONS растёт быстро (10K-100K записей в день). Без чистки таблица за год становится огромной. Авто-чистка:
- Celery beat
db_cleanup_dailyраз в сутки — удаляет закрытые сессии старшеDB_CLEANUP_SESSIONS_DAYS(default 90 дней); - Удаление в чанках по 5000 записей с паузами — чтобы не блокировать БД;
- VACUUM ANALYZE после удаления.
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-сессий клиента.

Карточка контекста тарифа
Сверху страницы — компактный градиентный блок с информацией о тарифе клиента: имя, цена, шейпер из TarifRadiusParams (Mikrotik-Rate-Limit), Acct-Interim-Interval, статистика 7 дней (всего сессий / обрывов / процент). При drop_pct > 20% — иконка ⚠ предупреждения «выше нормы по тарифу».
Toolbar с KPI и переключателем периода
Над таблицей — 5 KPI-чипов:
- Всего — количество сессий за выбранный период
- Online — сейчас активные (с пульсирующей зелёной точкой)
- Загрузка / Выгрузка — суммарный трафик ↓/↑
- Обрывов — % сессий с
END_REASON= Lost-Carrier и подобными. При >25% — красный цвет - MAC — количество уникальных MAC-адресов (выявление смены устройства)
Переключатель периода: Сутки / Неделя / Месяц / Всё. Клик → fetch GET /admin/Abonents/<ab_id>/sessions/kpi/?period=... → JSON → точечное обновление цифр.

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 на десктопе)
- Логин — имя учётной записи. Скрывается автоматически если у клиента всего 1 учётка — экономия места.
- Период — объединены 3 бывшие колонки. Дата сверху, время начала → конца на одной строке, аптайм с иконкой ⏱. Для активных — пульсирующая зелёная точка «● идёт».
- Update — время последнего
Acct-Interim-Update. - Причина — цветной бейдж с иконкой:
- ⏻ Норма — User-Request, Logout
- ⚡ Обрыв линии — Lost-Carrier, NAS-Reboot
- ⏳ Таймаут — Idle-Timeout
- ✨ Восстановлена — Synthetic, Recovered
- 🛡 Админ — Admin-Reset, NAS-Request
END_REASON. Полную расшифровку всех кодов см. в разделе Причины завершения сессии ниже. - Session — короткий
Acct-Session-Id(10 hex). Полный — в тултипе. Клик копирует в буфер с зелёной вспышкой и toast. - MAC —
Calling-Station-Id+ OUI vendor рядом (TP-Link, MikroTik, D-Link, Asus, Netgear, Xiaomi и др., 50+ префиксов). Клик копирует. - IP —
Framed-IP-Addressсиним моноширинным. Клик копирует. - NAS — имя NAS зелёным с иконкой 🖥, IP под именем. Клик открывает модалку редактирования NAS в новой вкладке (паттерн
?edit=<id>+ autoOpenFromQuery в nas_list.html). - PPPoE / Порт —
NAS-Port-Id. - VLAN —
Tunnel-Private-Group-Idбейджем. - Трафик — две строки с цветовой кодировкой: ↓ загрузка зелёным, ↑ выгрузка красным. В тултипе — точные байты.
- Статус — ● Online / ⚠ Stale / ✓ Завершена.
- Действия — кнопка «i» Подробности и «⏻» CoA Disconnect (только на активных). На Online-строке кнопка CoA — белая с красной обводкой для контраста.
Причины завершения сессии — полная расшифровка
Значение в колонке «Причина» — это RADIUS-атрибут Acct-Terminate-Cause, который NAS (как правило MikroTik) присылает биллингу в пакете Accounting-Stop при закрытии сессии. Записывается в RADIUS_SESSIONS.END_REASON. Биллинг группирует десятки возможных кодов в 5 категорий (классификатор _REASON_CATEGORIES в billing/templatetags/user_filters.py, фильтр reason_category):
| Бейдж | Что значит | RADIUS-коды (Acct-Terminate-Cause) | Что делать оператору |
|---|---|---|---|
| ⏻ Норма | Сессия завершена штатно — клиент сам отключился по своей инициативе или переавторизовался заново. | User-Request, Logout, Admin-Rebooting |
Ничего. Это нормальное поведение. |
| ⚡ Обрыв линии | Физический обрыв соединения: NAS перестал получать пакеты от клиента — выключение оборудования, проблема с кабелем, пропало питание, перезагрузка NAS. | Lost-Carrier, Lost-Service, Port-Error, NAS-Reboot, NAS-Error |
Единичный — норма (клиент выключил роутер). Много подряд у одного клиента → проблема с линией/оборудованием клиента, стоит проверить. KPI «Обрывов >25%» в toolbar — сигнал тревоги. |
| ⏳ Таймаут | Сессия завершена по таймауту — неактивность клиента или достигнута максимальная длительность сессии (настройки на NAS / в тарифе). | Idle-Timeout, Session-Timeout |
Обычно норма. Если мешает клиенту — проверить Idle-Timeout в настройках NAS / профиля. |
| ✨ Восстановлена | Запись восстановлена биллингом синтетически: пришёл Accounting-Stop без соответствующего Accounting-Start (пакет Start был потерян). Чтобы трафик не пропал из отчётов, биллинг создаёт закрытую «синтетическую» запись. |
Synthetic, Recovered (внутренние пометки биллинга, не от NAS) |
Единичные — норма (потеря UDP-пакета). Массово → возможны проблемы со связью NAS↔биллинг или перегрузка accounting-очереди. |
| 🛡 Админ | Сессия принудительно завершена со стороны провайдера, а не оборвалась сама. Это команда, а не сбой. | Admin-Reset, Admin-Reboot, NAS-Request, Callback-Reset, Manual-Reset |
Норма, если действие было намеренным: оператор нажал «Отсоединить» (CoA Disconnect), сработал автоблок по балансу, сменили тариф/IP, кто-то вручную убил PPPoE-сессию на MikroTik. Массово и неожиданно → проверить, кто и что делает с NAS. |
| — не классифицирована | NAS не указал причину, либо прислал код, которого ещё нет в классификаторе. В бейдже показывается raw-значение как есть. | Любой код вне списков выше, либо пустое значение | Посмотреть raw-значение в тултипе. Если код встречается часто — стоит добавить его в _REASON_CATEGORIES. |
Частые вопросы
NAS-Request≠ обрыв. Это категория «Админ» — сессию закрыли командой (CoA Disconnect от биллинга, ручное завершение на NAS, переназначение IP). Линия клиента при этом в порядке.Lost-Carrier= реальный физический обрыв. NAS просто перестал получать keepalive-пакеты от клиента. Это про оборудование/кабель/питание на стороне клиента.Admin reset/Manual-reset-by-operator— частные случаи категории «Админ»: сессию закрыл оператор кнопкой «Сбросить зависшую сессию» в карточке учётной записи.- Пустая причина у активной сессии — норма: сессия ещё не завершена,
END_REASONзаполнится только приAccounting-Stop.
Подсветка строк
- Online — салатовый фон
#d4f5ddс зелёной полосой 4px слева черезbox-shadow inset. - Synthetic (восстановленные сессии) — фиолетовая полоса 3px слева + слабый фиолетовый фон
rgba(112,72,232,0.04). - Related (имеют связанную Synthetic-сессию с тем же ACCT_SESSION_ID) — слабый фиолетовый фон
rgba(112,72,232,0.025).
Drawer-модалка деталей сессии
Клик на «i» в строке (или Enter на сфокусированной строке) открывает offcanvas-end справа шириной 540px с 4 вкладками:
- Обзор — 9 ключевых полей в
<dl>: Начало / Завершение / Длительность / MAC / IP / NAS / Port / VLAN / Причина.
- 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.

- Трафик — крупные цифры ↓ загрузка / ↑ выгрузка (в МБ/ГБ + точные байты), средний rate в Kbps, длительность.

- Действия — CoA Disconnect (disabled для закрытых) + 3 копировать-кнопки (MAC / IP / Session ID) + ссылка «Открыть NAS».

CoA Disconnect — разрыв сессии оператором
Кнопка ⏻ в строке с активной сессией (или из drawer-модалки → вкладка «Действия»). Клик открывает confirm-модалку с превью последствий:

Подтверждение → 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 с карточками:

Каждая сессия — карточка с зелёной полосой слева для 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-фиксы
- aria-label на каждой строке: «Сессия от 07.05.2026 13:53:08, активна».
- tabindex=0 — таблица навигируется с клавиатуры. Enter на сфокусированной строке открывает drawer.
- :focus-visible — outline 2px brand-зелёный.
- prefers-reduced-motion: reduce отключает все анимации (пульсация, flash, sparkline transition).
- scope="col" на всех
<th>+role="grid"+<caption class="visually-hidden">для скрин-ридеров. - Цвет НЕ единственный сигнал — везде есть иконки (fa-arrow-down/up для трафика, fa-circle для Online, fa-check для Завершена и т.д.).
- Время — все отметки показываются в московской зоне, без дополнительного сдвига.
- Кнопка разрыва сессии — красная: действие опасное. На подсвеченной строке Online она остаётся различимой (белый фон, красная обводка).
СОРМ
Паспортные данные и реквизиты для выгрузки в РКН по СОРМ-3:
- Физлицо: серия и номер паспорта, дата выдачи, кем выдан, код подразделения, дата рождения, ИНН, адрес регистрации.
- Юрлицо — отдельная карточка с 12 атрибутами: ИНН, КПП, ОГРН, юридический/физический/почтовый адреса, р/с, Банк, БИК, к/с, В лице, Директор. Бейдж «Юр.лицо» в шапке СОРМ. Тип определяется флагом
Abonents.company. - Прогресс-бар заполненности: процент от обязательных полей (для юр.лица учитываются дополнительные 12 атрибутов).
Реквизиты
Произвольные атрибуты клиента (UserAttributes + AttributeValues). Для юр.лиц при первом открытии вкладки авто-создаются 12 пустых атрибутов с default_person=True; для физлиц — с default_individual=True. Атрибуты можно использовать в шаблонах сообщений и печатных формах.
HelpDesk
Заявки HelpDesk, связанные с клиентом. Запросы идут через внутренний REST-клиент биллинга к HelpDesk. Из карточки можно:
- Просмотреть список тикетов: ID, статус, тема, дата, agent.
- Создать новый тикет (через внутренний endpoint биллинга).
- Перейти в HelpDesk по прямой ссылке.
Webhook-события из HelpDesk приходят обратно: conversation.agent_reply_created → push, conversation.assigned → Telegram админу, conversation.status_changed(closed) → AuditOperations.
Лояльность
Программы лояльности клиента и их история. Управляется через справочник Программы лояльности:
- Назначение программы (
AbonentsLoyalty) — задаёт скидку на тариф или дополнительные услуги. - Приоритет — если несколько активных, действует с большим
priority. end_date— программа автоматически снимается после этой даты.
Сообщения
История уведомлений клиента (MsgStack) по всем каналам:
- Email, SMS, Push, Telegram, VK, ЛК (winpopup).
- Заголовок (
head_txt), текст (text), время отправки (sent_at) или постановки в очередь (send_date). - Флаги done по каждому каналу (
email_done,sms_doneи т.д.). - Кнопка «Отправить новое сообщение» — выбор шаблона
AdminMsgили свободный текст.
Аудит
Журнал действий пользователей и системных событий по конкретному клиенту. Подвыборка из AuditOperations с фильтром по abonent_id + связанным USERS-записям через object_id.

Композиция
Сверху вниз:
- KPI-карточки — кликабельные плитки Всего, Финансовые операции, Услуги, Блокировки, Сообщения, Редактирование клиента, Другое со счётчиками. Клик по плитке = фильтр по этой категории. Карточка Всего в правом верхнем углу содержит i-иконку → ссылка на этот раздел документации.
- Toolbar (одна строка) — диапазон дат (компактные поля 96px без подписей «с/по», разделитель «—»), поиск по описанию, селект «Все типы», кнопка «Применить», кнопка экспорта CSV.
- Таблица с колонками Дата / ID, Событие, Описание, Исполнитель. Vertical-align top — длинные описания читаются проще. Колонка «Событие» = цветной лейбл + drill-иконка в одну строку (`flex nowrap`).
Период по умолчанию
При первом открытии вкладки сервер ставит диапазон последние 12 месяцев. Для более длинной истории — расширить вручную через datepicker. localStorage сохраняет выбранный текст-поиск и тип фильтра, но НЕ даты (даты могут устареть и ввести в заблуждение). Ключ хранилища: audit_filters_v2.
Дефолтный период (12 мес) настраивается в Настройки системы → Аудит — параметр AUDIT_TAB_DEFAULT_PERIOD_MONTHS (диапазон 1–60). Там же — лимит CSV-экспорта, записей на странице и опция «скрывать системные события».
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* |
| Прочее | светло-серый | остальные таблицы |
Банк
Вкладка «Банк» (иконка fa-university 🏛) — платежи клиента из
банковских выписок и выписанные ему счета с актами.
Появляется в карточке при наличии хотя бы одного платежа по выпискам.

- Шапка вкладки: сумма, зачисленная клиенту по выпискам, и кнопка
«В очереди модерации» → переход в очередь чеков/платежей,
отфильтрованную по этому клиенту (
?abonent=<id>). - Платежи из банковских выписок (
BankStatementOperation): дата, банк, сумма, плательщик, назначение, статус, ссылка на связанную финоперацию. - Счета и акты — сгенерированные PDF-документы с кнопкой открытия. Выписываются автоматически после зачисления платежа юр.лица (если включена автогенерация).
URL вкладки: /admin/Abonents/<id>/bank/. Подробное описание модуля —
в разделе Банковские выписки → Вкладка «Банк» в карточке.
Дополнительные действия
- Quick Add (модалка добавления клиента в папку) — поля Тариф, Группа, Юр.лицо, опционально NAS+IP. Фильтрация тарифов по флагу
is_businessдля юр.лиц. - Переезд в другую папку — drag-and-drop в дереве клиентов или через bulk-операцию Сменить папку.
- Печать: договор, акт, счёт-фактура — через
PrintForms(5.2). - Восстановление удалённого — после soft-delete клиент остаётся в БД с флагом
deleted=True; кнопка «Восстановить» в карточке возвращает в активное состояние.