Карточка клиента
Всё, что открывается в карточке клиента: вкладки, действия оператора, история изменений.
Карточка клиента
URL: /admin/Abonents/<id>/. Открывается кликом по строке клиента в любом списке (папка, глобальный поиск, модалка счёта) и сразу переходит на вкладку по умолчанию из настроек профиля — обычно это сводка /admin/Abonents/<id>/overview/. У каждой вкладки свой адрес (/operations/, /calls/, /messages/…): ссылку на нужную вкладку можно переслать коллеге.
flowchart TB
subgraph R1 [" "]
direction LR
L["Список клиентов
папка, поиск, счёт"] --> U["Карточка
/Abonents/ID/"]
end
subgraph R2 [" "]
direction LR
O["Сводка
/overview/"] --> T["Вкладки
/operations/ · /calls/ · /messages/"]
end
R1 -->|"вкладка по умолчанию
из настроек профиля"| R2
style R1 fill:none,stroke:none
style R2 fill:none,stroke:none
Где что лежит в карточке и в каком порядке этим пользуются: кто клиент, деньги, услуги и бонусы, связь, журнал изменений и переписка. Ролик для оператора, поддержки и бухгалтера — новый сотрудник смотрит его один раз вместо устного инструктажа.
Аватарка и соцсети клиента
Карточка показывает аватарку клиента (в шапке профиля, в сайдбаре-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 биллинг узнаёт клиента, когда тот пишет в соцсетях/мессенджерах — тикет сразу связывается с карточкой.
Шапка карточки
Над вкладками — строка с тем, что нужно видеть всегда:
- Номер договора и ФИО, рядом бейджем — тариф.
- Баланс — зелёный при плюсе, красный при долге. Крупные суммы сокращаются («43,9К ₽»), точная сумма — во всплывающей подсказке.
- Кнопки действий с клиентом — сохранение, печать, документы, блокировка, отключение, удаление. Опасные действия спрашивают подтверждение и простыми словами объясняют, что произойдёт.
- Бейдж организации — клик открывает смену организации клиента: окно предупреждает, что изменится, смена записывается в аудит.
- Кнопка с шапочкой открывает заново знакомство с карточкой, i — этот раздел документации.
Индикатор несохранённых изменений
Кнопка «Сохранить» (иконка-дискета в шапке карточки) показывает, есть ли в форме несохранённые правки, а изменённые поля подсвечиваются — оператор сразу видит, что нужно сохранить.
Состояния кнопки «Сохранить»:
- Серая, приглушённая — несохранённых изменений нет, сохранять нечего.
Шапка карточки: кнопка «Сохранить» серая — изменений нет - Зелёная с мягкой пульсацией — в форме есть несохранённые правки. Пульсация привлекает внимание, но не раздражает; уважает системную настройку
prefers-reduced-motion(при ней анимация отключается).
Шапка карточки: кнопка «Сохранить» зелёная — есть несохранённые изменения
Кнопка всегда физически кликабельна — серый вид это лишь подсказка «нечего сохранять», а не блокировка. Если клиент в этот момент обрабатывается биллингом (фоновая операция), кнопка не пульсирует, а tooltip объясняет, почему сохранение временно недоступно.
Подсветка изменённых полей. Любое поле формы, значение которого отличается от исходного, подсвечивается жёлтой рамкой и фоном (класс .ab-field-changed). Работает и для обычных полей, и для полей с выбором из списка; есть отдельный вариант для тёмной темы.
Как это работает. При загрузке страницы JavaScript снимает «снимок» исходных значений всех полей формы #changelist-form (часть полей догружается через AJAX — снимок повторяется через короткие интервалы, чтобы захватить и их). На событиях change/input текущее значение сравнивается со снимком: поле получает/теряет подсветку, кнопка «Сохранить» — зелёный вид. Технические поля (CSRF-токен, management-формы formset'ов) из отслеживания исключены.
Индикатор — это только визуальная подсказка поверх формы. Он не участвует в самом сохранении: клик по кнопке отправляет форму всегда, независимо от состояния индикатора.
Сводка клиента
Вкладка «Информация» открывается сводкой. Сверху — пять показателей, под ними — плашки «требует внимания», ниже — блоки в две колонки. Сводка отвечает на вопросы, с которыми звонят чаще всего, без переключения вкладок, а договор и контакты правятся прямо в блоках.
Показатели
| Карточка | Что показывает | Действие |
|---|---|---|
| Баланс | Сумма и на сколько её хватит, под ней — сколько в месяц списывается по услугам. Фон зелёный при плюсе и красный при долге. | «ОП» — обещанный платёж, если он доступен клиенту |
| Тариф | Цена в месяц и дата следующего списания. Если у тарифа нет своей цены и плата берётся услугами, показана сумма услуг с подписью «в месяц по услугам» — а не «0 ₽». | «Изменить тариф» |
| Связь | Активна или заблокирована, сколько учётных записей включено. При блокировке карточка красная: причина и с какого числа. | «Разблокировать» |
| Обращения | Открытых сейчас и всего. | «Создать» — новое обращение |
| За 30 дней | Итог движения денег: приход и расход. | «Все операции» |
Под показателями — плашки «требует внимания»: долг, незаполненный телефон или почта, открытые обращения. У каждой своя кнопка: «Открыть» ведёт к обращениям, «Дозаполнить» ставит курсор в нужное поле.
Блоки
Блок показывается, только если в нём есть что показать: у клиента без файлов нет блока «Файлы», а «Заполненность для СОРМ» пропадает, когда заполнены все поля.
| Колонка | Блок | Что внутри |
|---|---|---|
| Сверху | Плашки состояния | блокировка, обещанный платёж, диагностика связи |
| Сверху | Требует внимания | долг, незаполненные контакты, открытые обращения |
| Слева | Комментарии | заметки сотрудников с автором; удаление — с подтверждением |
| Слева | Платежи | последние поступления, средний платёж, в какой день клиент обычно платит |
| Слева | Что происходило | лента событий с фильтром «Деньги / Сообщения / Звонки / Оператор»; подробнее |
| Слева | Сессии | последние подключения: IP, причина разрыва; «Инфо» открывает боковую панель, «Разорвать» обрывает сессию |
| Слева | Аудит | кто из сотрудников что менял — с именем, а не номером |
| Слева | Сообщения | уведомления клиенту и дошли ли они |
| Справа | Контакты | почта, телефон, соцсети; правка на месте с проверкой дублей и домена почты |
| Справа | Заполненность СОРМ | полоса готовности данных к выгрузке |
| Справа | Адрес и подключение | адрес — улица, дом и квартира отдельно (подробнее), учётные записи, IP; пароль открывается по «глазу», просмотр пишется в аудит |
| Справа | Оборудование и сеть | техника со склада, точка подключения, порт |
| Справа | Договор | номер и дата договора, тип клиента |
| Справа | Услуги | активные услуги и сумма в месяц; по клику — список с ценами |
| Справа | Лояльность и скидки | программа лояльности клиента |
| Справа | Поддержка | последние обращения: открытые первыми, кто кого ждёт, ответственный, приоритет; клик открывает обращение в боковой панели |
| Справа | Звонки | последние разговоры, записи, AI-резюме |
| Справа | Файлы | вложения клиента |
| Справа | Задачи и наряды | открытые задачи CRM и выезды монтажников |
| Справа | Документы | договор, акты и счета клиента |
| Справа | Откуда пришёл | сделка CRM, рекламная кампания, UTM-метки |
| Справа | Клиент в других разделах | быстрые переходы по вкладкам |
Лента «Что происходило»
Лента собирает в одну колонку платежи и списания, сообщения клиенту, звонки и действия сотрудников. Под записью журнала стоит раздел словами — «Услуги», «Звонки», «Личный кабинет», — а не имя таблицы. Звонок подписан «Входящий звонок» или «Исходящий звонок» с номером, время — начало разговора, статус — словами. Суммы — со знаком и без копеек: «+8 089 ₽».
flowchart LR
D["Данные клиента
деньги, связь, обращения, звонки"] --> B["Сборка блоков сводки"]
S["Настройки → Профиль
какие блоки и в каком порядке"] --> B
B --> K["Показатели и плашки
«требует внимания»"]
B --> C{"Ширина экрана"}
C -->|"992 px и шире"| TWO["Две колонки"]
C -->|"уже"| ONE["Одна колонка
длинные блоки свёрнуты"]
Адрес и подключение: улица, дом и квартира
Адрес клиента состоит из двух частей, и хранятся они в разных местах. Дом — запись справочника адресов, одна на здание: по ней работают карта, проверка покрытия, выгрузка СОРМ и монтаж. Квартира — поле самого клиента. Поэтому адрес правится тремя полями.
| Поле | Как заполняется |
|---|---|
| Улица | Подсказки ФИАС от города до улицы — сюда же сёла и СНТ. Если выбрать город, он дописывается в поле, и подсказки продолжаются до улицы. |
| Дом | Подсказки только домов выбранной улицы. Номер можно вписать руками — сервер проверит его в ФИАС; номер, которого там нет, не принимается. |
| Кв | Обычное поле. В справочник адресов не попадает. |
По кнопке «Сохранить» сервер сам находит дом в ФИАС по улице и номеру, берёт запись справочника или заводит новую и привязывает к ней клиента. Если поменяли только квартиру, запись дома не трогается. У записанного адреса дома той же улицы подсказываются сразу, без повторного выбора улицы — по коду КЛАДР дома. Старый адрес без кода КЛАДР попросит выбрать улицу из подсказок.
Длинный адрес показывается в две строки; если не помещается и в две — обрывается многоточием, полный адрес виден во всплывающей подсказке. На телефоне подпись «Адрес» стоит над адресом, а карандаш — в строке подписи, чтобы адрес занимал всю ширину блока.
Подъезда в блоке нет: записанный подъезд при сохранении адреса из сводки не меняется.
Кто может менять адрес
Пустой адрес вписывает любой сотрудник с доступом к разделу «Клиенты». Заполненный меняют только Администратор, Директор, Старший сэйлс, Старший менеджер и суперпользователь — это право «Клиенты · Смена заполненного адреса» в блоке «Разделы админки», список ролей правится там же. У остальных вместо карандаша замок, а по нажатию окно объясняет, кто может изменить адрес.
Запрет проверяет и сервер: сохранение из блока отклоняется, а если адрес попытаться поменять через форму карточки, остаётся прежний. Смена записанного адреса всегда подтверждается окном «Было / Станет» — по адресу едет монтажник и уходит выгрузка СОРМ.
flowchart TB
E["Карандаш у адреса"] --> F{"Адрес уже записан?"}
F -- нет --> S["Улица · дом · кв"]
F -- да --> R{"Есть право на смену?"}
R -- нет --> L["Окно: кто может изменить"]
R -- да --> S
S --> C{"Меняется записанный адрес?"}
C -- да --> K["Подтверждение
«Было / Станет»"]
C -- нет --> V
K --> V["Сервер: дом из ФИАС →
запись справочника → аудит"]
Что пишется в аудит
| Запись | Когда |
|---|---|
| «Адрес указан из сводки: …» | адрес вписан впервые |
| «Адрес изменён из сводки: было → стало» | смена записанного адреса или квартиры |
| «В справочник добавлен дом из ФИАС: …» | дома ещё не было в справочнике |
| «Смена адреса отклонена — нет права: было → попытка» | попытка без права из блока |
| «Смена адреса в форме карточки отклонена — нет права: …; адрес оставлен прежним» | попытка без права через форму карточки |
На телефоне и в тёмной теме
На телефоне колонки складываются в одну. «Что происходило» уходит в самый низ, «Сессии» и «Аудит» — ниже «Поддержки»: сначала то, с чем работают, потом история. Длинные блоки сворачиваются по нажатию на заголовок, и какие блоки свёрнуты, браузер запоминает для сотрудника. В тёмной теме показатели сохраняют цвет состояния — долг и блокировка остаются красными.
Настройки профиля клиента (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 — Блоки сводки клиента
Какие блоки показывать в сводке клиента и в каком порядке. Список повторяет сводку: у каждого блока подпись, колонка (сверху, слева, справа) и пояснение. Порядок меняется перетаскиванием, видимость — переключателем, «Сбросить» возвращает порядок по умолчанию. Настройка общая для всех сотрудников и хранится в ABONENT_PROFILE_OVERVIEW_BLOCKS.
flowchart TB
subgraph R1 [" "]
direction LR
P["Настройки → Профиль
блоки сводки"] -->|"Сохранить"| DB["ABONENT_PROFILE_OVERVIEW_BLOCKS"]
end
subgraph R2 [" "]
direction LR
CACHE["Кеш профиля
60 секунд, сброс при сохранении"] --> CARD["Сводка клиента
видимость и порядок блоков"]
end
R1 --> R2
style R1 fill:none,stroke:none
style R2 fill:none,stroke:none
Секция 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 — Дефолты карточки
- Вкладка карточки по умолчанию: Информация / Финансы / Услуги / Точки подключения / Сессии / Поддержка
- Раскрытая секция accordion: Финансовая информация / Контакты / СОРМ / Доп. настройки
- Сортировка операций: новые сверху / старые сверху
- Период «Операции» по умолчанию: Месяц / Квартал / Год / Все. Применяется через JS+sessionStorage, не нарушая контракт «всё видно по умолчанию».
- Период «Сообщения» по умолчанию: Неделя / Месяц / Квартал / Все
Секция 4А — Управление вкладками карточки
Управление полным набором из 13 вкладок карточки клиента: видимость (показать/скрыть), порядок (drag&drop) и стиль отображения.
Возврат скрытых вкладок: «Задачи» (CRM_TASK, скрыта) и «Оборудование и подключения» — теперь можно вернуть в навбар одним кликом.
4 стиля отображения навигации вкладок (превью видно прямо в форме):
- Классический (default) — как сейчас
- 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_*(по умолчанию выкл.) - 📞 Свежее обращение в Поддержку — есть тикет < 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: все дефолты подобраны так, что поведение до фичи сохраняется при пустой БД настроек.
Диагностика связи — плашка-светофор
В шапке карточки клиента — автоматический экспресс-диагноз состояния связи. Светофор из 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, чтобы клиент переавторизовался. - 🔧 Тикет монтажнику — создаёт обращение в Поддержке с автозаполнением: ФИО / договор / телефон / адрес + результат диагноза + 🚨 подсветка 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. Используется в Поддержке-модуле, Telegram-боте, custom-скриптах. - CLI команда:
python manage.py diag <login>(или--contract,--abonent-id) — текстовый отчёт с цветовой подсветкой. Опция--jsonдля скриптов,--telegramотправляет компактную сводку в админ-чат алертов.
Пример CLI:
$ docker compose exec web python manage.py diag 100042
═══ Диагноз: 🟡 YELLOW — #100042 Иванов Иван Иванович ═══
[GREEN] billing — Активен, баланс 120.00 ₽, Домашний 300: 650.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 в контекст шаблона.
Единые компоненты карточки
Карточка складывалась годами, и каждая вкладка обзавелась своей вёрсткой: собственные таблицы, бейджи пяти оттенков, группы кнопок вместо чипов, окна браузера вместо диалогов. Внешне это читалось как разные разделы одной системы, а на телефоне часть вкладок просто прятала колонки.
Проверка по всем шаблонам карточки нашла и заменила:
| Что было | Сколько | Чем заменено |
|---|---|---|
| bootstrap-бейджи пяти подтонов | 62 | smit-badge с сохранением тона |
| синие кнопки | 12 | фирменные зелёные |
| скрытие колонок на телефоне | 32 | снято: строка рисуется карточкой с подписями |
нативные alert() | 23 | уведомления SmitToast |
| группы кнопок-фильтров | 3 | SmitChipBar |
| ряды кнопок в строке таблицы | до 6 иконок | одно меню SmitActions |
flowchart LR
CARD["Карточка клиента"] --> T["Таблицы вкладок"]
CARD --> F["Фильтры и счётчики"]
CARD --> A["Действия"]
T --> T1["smit-tbl
подписи ячеек"]
T1 --> T2["Десктоп: таблица"]
T1 --> T3["Телефон: карточка
с подписью у поля"]
T --> T4["SmitTable
набор колонок в профиле"]
F --> F1["SmitChipBar
полоса фильтров"]
F --> F2["smit-kpi-pill
счётчики"]
F --> F3["SmitStat
сводка операций"]
A --> A1["SmitActions
меню строки"]
A --> A2["SmitBulkBar
выбранные строки"]
A --> A3["SmitConfirm
подтверждение"]
Телефон: карточка вместо урезанной таблицы
Прятать данные на узком экране нельзя — оператор в поле открывает карточку именно ради того, что пряталось. У «Аккаунтов» скрывалось двенадцать колонок из пятнадцати: VLAN, трафик, дата последнего входа, NAS и MAC девайса. У «Операций» — баланс, услуга и исполнитель. У «Поддержки» — агент и время решения.
Теперь таблицы карточки размечены классом smit-tbl и подписью
data-label у каждой ячейки: ниже 768 px строка превращается в
карточку, где подписано каждое поле, и ничего не исчезает.
flowchart TB
R["Строка таблицы
15 ячеек с data-label"] --> W{"Ширина экрана"}
W -->|"768 px и шире"| D["Таблица
колонки по настройке сотрудника"]
W -->|"уже 768 px"| M["Карточка
каждое поле с подписью"]
D --> DK["Действия: меню в строке"]
M --> MK["Действия: то же меню
палец попадает"]
Набор колонок — в профиле сотрудника
Кнопка выбора колонок везде одинаковая: квадратная, с иконкой столбцов и
подписью в подсказке, на телефоне скрыта — там настраивать нечего. Набор,
порядок и ширины хранятся в профиле сотрудника (SmitTable), а не в
браузере, поэтому переезжают на другую машину.
Колонки, которые нужны время от времени, по умолчанию скрыты и включаются кнопкой. У «Аккаунтов» это коммутатор с портом (обход по подъезду), комментарий монтажника и дата создания учётной записи — раньше за ними приходилось открывать отдельную карточку.
Действия строки — одним меню
В строке учётной записи стояло шесть кнопок пятью цветами: правка синяя, отключение жёлтое, тест голубой, удаление красное. На широком экране это пестрило, на телефоне не помещалось.
Компонент SmitActions получил метод collapse: он
сворачивает готовые кнопки строки в одно меню, читая подпись,
иконку и «опасность» из самой кнопки, а по выбору нажимает исходную. Обработчики
переписывать не нужно — меняется только вид. Короткую подпись пункта можно
задать атрибутом data-act-label, иначе берётся title
без скобочного пояснения.
SmitActions.collapseRows('#usrTable', {label: 'Действия с учётной записью'});
flowchart TB
subgraph R1 [" "]
direction LR
B["Готовые кнопки строки
правка · отключить · тест · удалить"] --> C["SmitActions.collapse"] --> READ["Читает из кнопки:
подпись, иконку, опасность"]
end
subgraph R2 [" "]
direction LR
MENU["Меню строки"] -->|"выбор пункта"| CLICK["Нажимает исходную кнопку"] --> H["Прежний обработчик
переписывать не нужно"]
end
R1 --> R2
style R1 fill:none,stroke:none
style R2 fill:none,stroke:none
Поиск по карточке
Вкладок пятнадцать, полей на них — под две сотни. «Где комментарий
монтажника», «на какой вкладке паспорт» оператор помнит не всегда. В строке
вкладок стоит поиск (SmitSectionSearch): подсказывает поле и
открывает нужную вкладку, вызывается Ctrl+K из любого
места страницы.
Обзор карточки: что где лежит
Есть обучающий ролик. Те же шесть шагов меньше чем за три минуты, с озвучкой — его показывают новому сотруднику вместо устного инструктажа: плеер в начале раздела.
Вкладок до семнадцати, и по названию не всегда понятно, что внутри. Часть появляется, только когда включён модуль («Видео», «Оборудование», «Банк», «Маркетинг») или есть что показать («Звонки», «Реквизиты» у юр.лиц). Ниже — карта карточки и снимки групп вкладок, сняты с демонстрационного клиента, у которого наполнены и деньги, и связь, и переписка.
flowchart TB
H["Шапка: договор, ФИО, тариф, баланс, действия"] --> N["Полоса вкладок + поиск по карточке (Ctrl+K)"]
N --> G1["Кто клиент"]
N --> G2["Деньги"]
N --> G3["Связь"]
N --> G4["История и общение"]
G1 --> I["Информация
сводка"]
G1 --> S["СОРМ"]
G1 --> R["Реквизиты
у юр.лиц"]
G2 --> O["Операции"]
G2 --> U["Услуги · Бонусы"]
G2 --> L["Лояльность"]
G2 --> BK["Банк · Маркетинг"]
G3 --> A["Аккаунты"]
G3 --> SE["Сессии"]
G3 --> E["Оборудование · Видео"]
G4 --> AU["Аудит"]
G4 --> M["Сообщения"]
G4 --> C["Звонки"]
G4 --> SU["Поддержка · Файлы"]
У каждой вкладки слева вверху стоит её название и значок i с
короткой подсказкой: что здесь делают и на что смотреть. Если у вкладки есть
панель инструментов, название встаёт с ней в одну строку. Текст подсказок живёт
во вьюхе (_TAB_HINTS в abonents.py).
Кто клиент
Показатели отвечают на «сколько должен» и «работает ли связь», плашки — что сделать прямо сейчас, блоки — кто клиент и как он подключён. Пустые обязательные поля подсвечены, «Дозаполнить» ставит курсор в нужное поле.
Деньги: от платежа до снятия блокировки
Вкладка «Операции» отвечает на два вопроса: сколько клиент должен и почему. Карточка «Всего операций» показывает график за 30 дней — по нему видно, когда у клиента что-то происходило, без чтения таблицы. Список догружается прокруткой, колонки настраиваются под себя, отмеченные строки суммируются.
flowchart LR
P1["Платёж
касса, банк, ЛК"] --> FO["Финансовая операция"]
P2["Абонплата
ежемесячно"] --> FO
P3["Обещанный платёж"] --> FO
FO --> BAL["Баланс клиента"]
BAL -->|"стал положительным"| UNB["Снятие финблокировки"]
BAL -->|"ушёл в минус"| BL["Автоблокировка"]
UNB --> MSG["Уведомление клиенту"]
BL --> MSG
FO --> AUD["Запись в аудит"]
Сторно не удаляет операцию, а добавляет обратную и связывает её с исходной: история остаётся целой, а строка получает свой бейдж.
«Услуги» и «Бонусы» — одна таблица с разным отбором. В «Бонусы» попадает то, что уменьшает счёт: нулевая или отрицательная цена, скидки и компенсации. Пустая индивидуальная сумма означает, что берётся цена из шаблона услуги — источник подписан прямо в окне правки.
Связь
Учётные записи — то, по чему работает авторизация в сети: логин, пароль, IP, точка подключения. Колонки прячутся не на телефоне, а по выбору сотрудника: на узком экране строка превращается в карточку с подписями у полей.
История: аудит, сообщения, обращения
Сводка вкладки «Аудит» — те же карточки метрики, что и в операциях: клик по категории оставляет в журнале только её события. В колонке «Событие» — раздел словами, а текст записи очищен от внутренних кодов: робот-звонок записан как «Робот-звонок, сценарий «Напоминание об оплате»: начат» (подробнее).
В «Сообщениях» видно не только что отправили, но и чем закончилось: значок канала несёт статус доставки, а входящие ответы клиента отмечены полосой слева. Каналы переключаются полосой чипов, период — единым компонентом. Если клиент в стоп-листе или отписался от рассылок в мессенджере, над формой видна строка запрета, а неотправленное SMS помечено «SMS пропущено» с причиной.
Помощник знает, где вы стоите
AI-помощник в карточке получает три вещи: выжимку по клиенту, открытую вкладку и отмеченные галочками строки. Вопрос «а что тут?» на вкладке операций и на вкладке услуг — разные вопросы, и отмеченные строки почти всегда и есть предмет вопроса. Полная выжимка по клиенту остаётся всегда: разбирать операцию, не зная баланса и блокировок, бессмысленно.
flowchart LR
UI["Карточка клиента"] -->|"вкладка + отмеченные строки"| ASK["Запрос помощника"]
DB["Данные клиента
баланс, блокировки, услуги, обращения"] --> ASK
ASK --> ANS["Ответ по делу"]
ANS --> DRAFT["Черновик письма клиенту
отправляет сотрудник"]
Вкладки, которые не поместились, — в «Ещё»
У каждой вкладки остаётся подпись: значки без подписей сотрудники не узнавали. Вкладки, которые не поместились в ширину, уходят в меню «Ещё N» справа — со значками и счётчиками. Четыре первые вкладки видны всегда.
Открытая вкладка всегда на виду. Если её открыли из «Ещё», она встаёт последней из видимых, а остальные остаются на своих местах: порядок не меняется от перехода к переходу, и нужная вкладка каждый раз на том же месте. Только если подпись слишком длинная и последней не помещается, вкладка встаёт второй.
Полоса пересчитывается при загрузке страницы, после загрузки шрифтов и при изменении ширины окна. На телефоне (уже 768 px) «Ещё» нет: полоса прокручивается, открытая вкладка встаёт в начало.
flowchart LR
A{"Все вкладки
помещаются?"} -->|"да"| F["Полоса без «Ещё»"]
A -->|"нет"| B["Не поместившиеся —
в «Ещё N»"]
B --> C{"Открытая вкладка
в «Ещё»?"}
C -->|"нет"| G["Порядок не меняется"]
C -->|"да"| D["Встаёт последней
из видимых"]
Знакомство с карточкой
При первом открытии карточки сотрудник видит короткое знакомство: первым экраном — обучающий ролик, дальше пять шагов с обложками — вкладки, деньги, связь, история и AI-помощник. Обложки подстраиваются под светлую и тёмную тему. Знакомство показывается один раз, открыть его заново — кнопкой с шапочкой в шапке карточки.
Пустая карточка ничему не учит. Если разворачиваете
систему с нуля и хотите посмотреть карточку на данных, есть команда
manage.py seed_demo_abonent: она заводит клиента с договором
DEMO-0001 и наполняет вкладки — платежи и начисления за полгода,
услуги, бонус, истёкший обещанный платёж, сторно, переписка по каналам.
--wipe удаляет его вместе со всеми записями. Справочники команда
не трогает: тариф, услуги, адрес и программа лояльности берутся
существующие — свои попали бы в прайс и отчёты как настоящие.
Вкладки
Вкладки с подробной информацией. Часть из них объединена в группы: «Учётные записи + RADIUS», «Точки подключения + Оборудование» — соседние вкладки навигируются стрелками. Некоторые вкладки условные: «Банк» (иконка fa-university 🏛) появляется, когда у клиента есть платежи по банковским выпискам.
Информация
Вкладка открывается сводкой клиента. Поля формы — тариф, договор, контакты, адрес, признак юр.лица — встроены в блоки сводки и сохраняются кнопкой «Сохранить» в шапке; комментарии добавляются в блоке «Комментарии». Ниже — правила этих полей.
Чекбокс «Юр. лицо» и валидация тарифа
При включении чекбокса «Юр. лицо» на физлице с физическим тарифом (Tarif.is_business=false) JavaScript показывает confirm-диалог и при подтверждении открывает модалку смены тарифа с автоматическим переключением на фильтр «Юр.лица». Серверная валидация (Abonents.clean()) дополнительно проверяет: если company=true и текущий тариф не Business — ValidationError на поле tarif с понятным текстом.
Маски и валидация email/телефон
Оба контактных поля имеют клиентскую и серверную валидацию:
- Телефон (
sms): ввод по маске+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), чтобы оператор видел все проблемы сразу.
Проверка домена почты в сводке
Правильный по форме адрес ещё не значит, что письмо дойдёт: ivan@gmail.ru или ivan@iclaud.com
проходят проверку формата, но письма туда возвращаются или уходят постороннему. Поэтому при сохранении почты в блоке
«Контакты» сводки биллинг проверяет домен:
- опечатка — домен на одну-две буквы отличается от известного сервиса или у него не та зона
(
gmail.ru→gmail.com,mai.ru→mail.ru,yondex.ru→yandex.ru); - домен без почты — такого домена нет, у него нет почтового сервера или вместо сервера заглушка.
flowchart TD
A["Оператор вводит почту
и нажимает «Сохранить»"] --> F{"Формат адреса
верный?"}
F -- "нет" --> E["«Это не похоже на адрес почты»"]
F -- "да" --> T{"Домен похож на
опечатку или без почты?"}
T -- "нет" --> D{"Адрес уже есть
у другого клиента?"}
T -- "да" --> W["Окно «Проверьте адрес почты»"]
W -- "Исправить на …" --> D
W -- "Сохранить как есть" --> D
W -- "ввести заново" --> F
D -- "нет" --> S["Сохранено"]
D -- "да" --> Q["Подтвердить дубль"] --> S
Это предупреждение, а не запрет: если клиент действительно пользуется таким адресом, выберите «Сохранить как есть».
Если DNS не ответил, проверка по DNS пропускается — сохранить хороший адрес она не мешает. Ответы DNS запоминаются на сутки.
Найти такие адреса, записанные раньше, — команда clean_bad_emails --domains
(«Письмо ушло, но не дошло»).
Блок «Смена тарифа / Блокировки»
Внутри блока 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):
- Изменение тарифа — выбор из списка с предпросмотром стоимости. Можно задать «Тариф следующий» (
tarif_next) для отложенной смены с начала следующего расчётного периода. - Список услуг с фильтрами enabled / disabled / deleted. Каскад отображения суммы:
UU.summ → Usluga.summa → Usluga.price; источник указывается явно в плейсхолдере. - Модалка редактирования: Услуга (поиск по названию), Индивидуальная сумма, Скидка, Кол-во, Дата начала, Дата следующего списания, Примечание, Включена.
- Опция «Очистить сумму» возвращает каскадное вычисление (
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), Адрес (дом с поиском, квартира), 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.
Панель сессии — «RADIUS» - Трафик — крупные цифры ↓ загрузка / ↑ выгрузка (в МБ/ГБ + точные байты), средний 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. Атрибуты можно использовать в шаблонах сообщений и печатных формах.
Поддержка
Обращения клиента в Поддержку: номер, тема с числом сообщений, статус, ответственный агент, время решения и дата обновления. У открытых обращений время решения показывает, сколько они уже открыты, у закрытых — сколько заняло решение; закрытые строки приглушены. Стрелка в строке открывает обращение в Поддержке, «Создать тикет» — окно нового обращения; кнопка «Создать» на карточке «Обращения» в сводке ведёт сюда и сразу открывает это окно.
Лояльность
Программы лояльности клиента и их история. Управляется через справочник Программы лояльности:
- Назначение программы (
AbonentsLoyalty) — задаёт скидку на тариф или дополнительные услуги. - Приоритет — если несколько активных, действует с большим
priority. end_date— программа автоматически снимается после этой даты.
Сверху — счётчики: всего, активных, будущих, истекают в ближайшие 14 дней, завершены; рядом — максимальная скидка из активных программ и прямая скидка, заданная в карточке вручную. Клик по счётчику фильтрует таблицу; то же делает полоса чипов под ними.
Сообщения
Слева — форма «Отправить сообщение»: треть ширины, при прокрутке остаётся на экране. Справа — история на две трети.
- Каналы — только те, по которым сообщение дойдёт: личный кабинет всегда, SMS — если указан телефон, e-mail — если указан адрес, Telegram и push — если клиент их привязал, VK — если клиент входил в личный кабинет через VK или прошёл проверку в чате с ботом. Сообщение в VK уходит от сообщества организации клиента.
- Тихий час — строка под заголовком формы с окном из «Настройки → Сообщения»; пока окно действует, строка оранжевая. SMS и push в это время не отправляются: сообщение получает статус «пропущено».
- Стоп-лист — строка под тихим часом, если клиенту нельзя присылать рекламу или ничего вовсе, и отписки от рассылок в мессенджерах. «Изменить» открывает выбор области, строка обновляется сразу. Подробнее о стоп-листе.
- Шаблон, заголовок и текст; кнопки «Отправить» и «Очистить».
- Подстановки — долг, номер договора и другие переменные шаблона заполняются при отправке, и в историю записывается уже готовый текст. В карточке, личном кабинете и приложении клиент видит те же цифры, что пришли ему в SMS. До сборки 3139 в историю попадал шаблон с переменными, и кабинет показывал клиенту «%(debt)s».
- История: каналы — полосой чипов, рядом «Найдено: N · стр.»; дата и время в две строки; значок канала показывает, дошло ли сообщение; время отправки — со значком часов; ответы клиента отмечены полосой слева. Неотправленное SMS помечено «SMS пропущено», причина — словами в подсказке: тихий час, клиент отказался от рекламы, то же сообщение ушло недавно.
- Рассылки бота — таблица под историей: рассылки Salesbot по чатам клиента из журнала отправок — дата, канал, текст и статус («Доставлено», «Бот заблокирован», «Ошибка» с числом попыток). В общую историю они не попадают: рассылка идёт в чат, а не клиенту.
flowchart TB
F["Форма
шаблон, текст, каналы"] --> Q["Очередь сообщений"]
Q --> P{"Правила отправки"}
P -->|"нельзя"| SK["Пропущено: стоп-лист, канал выключен,
тихий час для SMS и push, повтор текста"]
P -->|"можно"| J["Журнал отправок:
уже ушло — не повторяем"]
J --> SEND["Отправка по каналам"]
SEND --> H["История
статус в значке канала"]
SK --> H
Звонки
Разговоры с клиентом из телефонии: входящие и исходящие, записи, расшифровки и AI-разбор. Вкладка появляется, когда включён модуль автообзвона и у клиента есть телефон или хотя бы один звонок: без телефона история видна, но позвонить нельзя.
- Первая строка — согласие клиента на голосовые звонки (152-ФЗ), тихий час, сценарий, «Звонок ботом» и «Автодозвон» (Mango соединяет оператора с клиентом). В тихий час не звонит автообзвон по расписанию; ручные «Звонок ботом» и «Автодозвон» не запрещены, но в это окно карточка сначала предупреждает и спрашивает подтверждение.
- Метрики за период: всего звонков, входящие, исходящие, без разговора, средняя длительность, расшифровано, эскалации. Считаются по всем звонкам периода, а не по показанной странице.
- Фильтр — меню со счётчиками: входящие, исходящие, с разговором, без разговора, расшифрованные, эскалация. Пункты одной группы складываются через «или», разных — через «и». Рядом период (по умолчанию — текущий год), поиск по номеру и тексту разговора и настройка колонок.
- Таблица: когда, направление, длительность, статус, суть звонка (AI-категория), о чём разговор, запись. Время — начало звонка. «Без разговора» — длительность ноль и записи нет. Колонка «Номер» скрыта по умолчанию: в карточке клиента номер почти всегда его собственный, её включают в настройке колонок.
- Раскрытие звонка — номер, сделка CRM и последнее обращение клиента, встроенный плеер записи (перемотка, скорость, скачать), «О чём разговор (AI)» и текст разговора абзацами; «Обновить AI-разбор» заново расшифровывает запись.
- Отмеченные звонки: скопировать номера, скачать CSV и AI-разбор — только звонков с записью и после подтверждения, потому что расшифровка расходует токены.
flowchart LR
T["Телефония"] --> V["Звонок
начало, номер, запись"]
V --> A["Привязка к клиенту"]
V --> R["Расшифровка
и AI-разбор"]
A --> TAB["Вкладка «Звонки»"]
R --> TAB
TAB --> M["Метрики и фильтр
за выбранный период"]
Аудит
Журнал действий пользователей и системных событий по конкретному клиенту. Подвыборка из AuditOperations с фильтром по abonent_id + связанным USERS-записям через object_id.
Композиция
Сверху вниз:
- KPI-карточки — кликабельные плитки Всего, Финансовые операции, Услуги, Блокировки, Сообщения, Редактирование клиента, Другое со счётчиками. Клик по плитке = фильтр по этой категории. Карточка Всего в правом верхнем углу содержит i-иконку → ссылка на этот раздел документации.
- Toolbar (одна строка) — диапазон дат (компактные поля 96px без подписей «с/по», разделитель «—»), поиск по описанию, селект «Все типы», кнопка «Применить», кнопка экспорта CSV.
- Таблица с колонками Дата / ID, Событие, Описание, Исполнитель. Vertical-align top — длинные описания читаются проще. Колонка «Событие» — раздел словами, цвет по группе разделов, рядом — ссылка на изменённый объект.
Период по умолчанию
При первом открытии вкладки сервер ставит диапазон последние 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 — на услугу.
Текст записей
Журнал пишут разные модули, и в запись попадала внутренняя кухня: имя таблицы, код сценария робота, название платформы телефонии. Перед показом текст переводится на язык оператора — одинаково в сводке, на вкладке «Аудит» и в боковой карточке клиента. Сами записи в базе не меняются.
users_usluga→ «Услуги».- «Автообзвон (…): робот-звонок … по сценарию «low_balance» → инициирован» → «Робот-звонок …, сценарий «Напоминание об оплате»: начат»; если звонок не получился — «не удался».
Название сценария берётся из справочника сценариев автообзвона; неизвестный код показывается как есть. Новые записи о робот-звонках пишутся сразу в таком виде.
Разделы событий
Раздел определяется по таблице, которую изменило действие. Цвет общий для группы разделов; в тёмной теме — светлые оттенки тех же цветов.
| Раздел | Цвет | Таблицы |
|---|---|---|
| Услуги | зелёный | USERS_USLUGA |
| Операции | тёмно-зелёный | FINANCE_OPERATIONS |
| Лицевые счета | тёмно-зелёный | ADMIN_ACCOUNTS |
| Оплата | бирюзовый | PAY_LOG |
| Банк | бирюзовый | BANK_* |
| Блокировки | оранжевый | ABONENTS_BLOCK |
| Статусы | янтарный | STATUS, OBJECTS_STATUS |
| Карточка клиента | графитовый | ABONENTS, ATTRIBUTE_VALUES |
| Лояльность | графитовый | ABONENTS_LOYALTY |
| Адрес | графитовый | HOMES |
| Сообщения | серо-синий | MSG_STACK |
| Звонки | серо-синий | VOICE_CALL |
| Обращения | серо-синий | SUPPORT_CONVERSATION |
| СОРМ | серый | SORM* |
| Личный кабинет | светло-серый | LK_* |
| Учётные записи | светло-серый | USERS |
| Сессии | светло-серый | RADIUS_SESSIONS |
| Видеонаблюдение | светло-серый | VIDEO_* |
| Оборудование | светло-серый | STOCK_MOVEMENT |
| Телевидение | светло-серый | IPTV_ACCOUNT_MAPPING |
| Документы | светло-серый | DOCUMENT |
| Прочее | светло-серый | остальные таблицы |
Банк
Вкладка «Банк» (иконка 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; кнопка «Восстановить» в карточке возвращает в активное состояние.