Клиенты
Список клиентов и папки, поиск и карта подключений. Счета, финансовые операции, обещанный платёж и блокировки — на странице «Деньги клиента».
Содержание раздела
Тот же механизм, но нарисованный: узлы можно подсвечивать, связи — разбирать по шагам. Открывается в новой вкладке.
Абонент по балансу
Состояния
Блокировка и возврат доступа
Последовательность
Финблокировка и заглушка
Архитектура
Клиенты
Раздел меню «Клиенты» — основной рабочий раздел оператора.
Папка и глобальный поиск, счётчики-фильтры и цвет строки, одиннадцать массовых действий, создание клиента с учётной записью и IP, карточка клиента и карта.
Папки клиентов — назначение и роль
Содержание раздела
Папка клиентов — это «контейнер-узел» в дереве клиентов с флагом IS_FOLDER=true. Хранится в той же таблице abonents (поле parent_id), но не имеет тарифа, счёта и финансовых атрибутов. Корневая папка имеет ID=9001.
Зачем нужны папки
Папки решают три задачи:
- Логическая группировка — клиенты раскладываются по агентам, филиалам, типам клиентов, географическим зонам. Это удобно для навигации и фильтрации в админке;
- Контроль доступа операторов — через
AbonentGroupAccessDjango-группа привязывается к конкретным папкам, и оператор видит только своих клиентов; - Корзина удалённых — обычно это спец-папка (#9003 «Удалённые»), куда перемещаются
deleted=true-клиенты для возможного восстановления.
Как устроено дерево
Иерархия держится на parent_id — FK таблицы на саму себя:
parent_id—NULLу корня (#9001), у остальных — ссылка на родителя. Это единственный источник правды об иерархии;- Подпапки поддерживаются, глубина не ограничена одним уровнем;
- FK с PROTECT — нельзя удалить папку, у которой есть дочерние записи. Сначала перенесите или удалите детей.
lft / rght / tree_id / level не используйте.
У новых записей они не заполняются: модель
Abonents не подключена к MPTT-менеджеру, а значит
rebuild() и get_descendants() недоступны. Код,
который читает эти поля, обязан иметь fallback на parent_id —
как это сделано в выборке клиентов по папкам
(views/messages_global.py). Обход поддерева пишется обходом
по уровням через parent_id.
Группы и контроль доступа
Связь «Группа Django ↔ Папка клиента» хранится в AbonentGroupAccess:
- Каждая запись — это «группа GroupX имеет доступ к папке #N и её потомкам»;
- Один оператор может состоять в нескольких группах — тогда видит клиентов из объединения папок;
- Настраивается в
/admin/settings/abonent_group_permissions/(дерево с чекбоксами); - Если у оператора нет ни одной записи AbonentGroupAccess — он видит всех клиентов (нет ограничений по умолчанию);
- Поиск по папкам, экспорт CSV, bulk-actions — все они учитывают этот фильтр.
Папка организации
При включённой мультиорганизации папку верхнего уровня (лежащую внутри корневой «Все») можно закрепить за компанией — она становится её «домом»: туда складываются новые клиенты этой организации, и по ней строятся списки директорий в карточках тарифов.
- Где: карточка папки → кнопка «Изменить» рядом с заголовком. Там же переименование. Вложенным папкам организация не назначается — её задаёт ближайший закреплённый предок;
- Каскад: закрепление проходит вглубь — подпапки, клиенты и их финансовые операции получают организацию папки. Без этого у вложенных записей оставалась бы прежняя компания: в списках горел бы чужой бейдж, а фильтр «Орг.» их бы не находил;
- Счета собственной организации не имеют — они скоупятся через клиента, поэтому в каскад не входят;
- Цвет: иконка закреплённой папки красится акцентным цветом организации из брендинга и наследуется вложенными папками. Служебные папки (Корзина, Операторы связи) организации не имеют и остаются серыми.
Что делать со старыми папками
- Пустая папка без активных детей — можно удалить через UI. FK PROTECT защитит если в ней не считая deleted-клиентов есть живые;
- Папка с deleted-клиентами — нельзя удалить, soft-deleted записи всё ещё ссылаются на parent_id. Сначала «жёстко» удалить deleted-клиентов или перенести в общую корзину;
- Папка «Корзина» (#9003) — не удалять. Это служебная папка-приёмник для восстановления клиентов из архива;
- Перемещение клиента в другую папку — bulk action
move_folderв глобальном поиске (11 операций).
Abonents.delete() в коде выполняется soft-delete: deleted=true + enabled=false, клиент остаётся на своём месте в дереве. Если хотите централизованную «Корзину» — настройте автоматическое перемещение в #9003 через bulk action или добавьте логику в delete() модели.
Клиенты
Главный список клиентов. Можно работать в двух режимах: дерево папок (по умолчанию) и глобальный поиск. Папки удобны для иерархической навигации по агентам/филиалам/адресам, глобальный поиск — для массовых операций над всей базой.
Папка клиентов
Папка клиентов (/admin/Abonents/<id>/, корень — /admin/Abonents/9001/) — список клиентов конкретной папки с поддержкой подпапок. Каждая папка имеет свой набор быстрых фильтров и массовых действий, рассчитанный на ежедневную работу оператора с одним сегментом базы.
Содержание раздела
KPI и статистика
Над таблицей выводятся 4–5 счётчиков по клиентам текущей папки (без учёта подпапок): Активных, Сейчас онлайн, Выключенных, Всего, Юр. лиц. Числа обновляются вместе с применённым фильтром-чипом.
Чипы быстрых фильтров
Под счётчиками — ряд чипов с готовыми фильтрами. Активен может быть только один. Повторный клик снимает фильтр. Состояние сохраняется в URL — ссылку можно отправить коллеге:
- Должники — баланс счёта < 0.
- Бизнес — юр. лица (
company=true). - Без email / Без телефона — пустые поля контактов (чтобы дополнить данные).
- Онлайн — сейчас в RADIUS-сессии (< 15 мин с last update).
- Заблокированные — есть активная запись в
ABONENTS_BLOCK(admin / negbal / own). - Обещ. платёж — активен ОП (Usluga 30353).
Поиск, сортировка и multi-tarif
- Поиск в шапке таблицы по ФИО, номеру договора, e-mail, телефону. Минимум 2 символа, мгновенный фильтр.
- Шорткат Ctrl+K — фокус в поле поиска из любого места страницы.
- Сортировка по любой колонке кликом на заголовок (▲▼). Стрелка показывает направление.
- Multi-tarif фильтр — checkbox-список тарифов с поиском по названию. Позволяет ограничить выборку папки одним или несколькими тарифами одновременно.
Колонки таблицы
- ☑ — чекбокс выбора для массовых операций.
- № — внутренний ID клиента.
- ФИО — клик открывает карточку.
- Дог. — номер договора.
- Тариф — с иконкой 💼 для бизнес-тарифов.
- Адрес — короткий формат (через фильтр
home_short: ВО → Волгоградская обл., Влг → Волгоград). - Контакты — иконки ✉ / 📞 с tooltip-значениями. Серая иконка = пустое поле.
- Баланс — баланс счёта. Рядом 🤝 при активном обещанном платеже.
- Подкл. — дата подключения.
- 📡 — флаг «онлайн сейчас» (RADIUS-сессия < 15 мин).
- Статус — цветной бейдж: Активен / Заблок. / Удалён.
- ℹ Подробнее — кнопка открывает drawer-панель без перехода на полную карточку.
Подсветка строк (класс строки задаётся в abonents_folder_rows.html):
- Розовый фон — блокировка за неуплату: есть запись
в
ABONENTS_BLOCKс флагомb_negbal. Просто минус на счёте строку не красит — пока автоблокировка не сработала, клиент выглядит обычным; - Жёлтый фон — любая другая блокировка: административная или добровольная («отпуск»);
- Красный фон — клиент выключен (
enabled=false) и при этом не заблокирован; - Голубой фон — это не клиент, а папка;
- Усиленный красноватый фон добавляется поверх остальных при долге
более 500 ₽ (
is_deep_debt).
Тумблер «Цвет маркер» над таблицей гасит подсветку целиком; выбор запоминается за оператором.
Бесконечная подгрузка
Папка не использует пагинацию. При прокрутке вниз автоматически догружается следующая порция (по 100 записей) через AJAX-endpoint /admin/Abonents/<id>/children_json/. На больших папках (1000+ клиентов) загрузка остаётся плавной — браузер рендерит только видимый кусок.
Массовые действия и экспорт
Чекбокс в первой колонке + «Выбрать все на странице» в шапке. После выделения появляется меню «Действия (N)» — те же 11 операций, что и в глобальном поиске: включить / выключить / удалить / изменить тариф / точку подключения / перенести в другую папку / добавить услугу / бонус / финоперацию / лояльность / отправить сообщение. Destructive-операции защищены confirm-модалкой с предпросмотром имён, для большинства — кнопка «Откатить» в баннере на 1 час.
Экспорт CSV — кнопка «📥 CSV» в шапке выгружает текущий срез папки (/admin/Abonents/<id>/export/): UTF-8 BOM, разделитель «;» для Excel RU. Колонки совпадают с видимыми в таблице плюс расшифровка тарифа, оператора, баланс в рублях. Учитываются текущий фильтр-чип и текстовый поиск.
Навигация по подпапкам
Если у текущей папки есть подпапки — они выводятся отдельным блоком над таблицей клиентов: карточки с именем подпапки и счётчиком клиентов внутри (включая глубоких потомков). Клик переходит в подпапку (отдельный URL). Хлебные крошки в шапке страницы показывают полный путь от корня (#9001 → город → улица → дом), кликабельны.
Мультиорганизация
Выбор организации в шапке — это фильтр отображения. Пока выбрана конкретная компания, раздел показывает только её данные:
- Дерево слева — папки чужих компаний скрыты. Корневая «Все» видна всегда, иначе дерево схлопнулось бы в пустоту;
- Содержимое папки — и первая отрисовка, и подгрузка при прокрутке;
- Колонка «Орг.» — показывается при мульти-орг и режиме «Все организации», в папке после номера строки, в глобальном поиске — после «ФИО / Компания»;
- Записи без организации остаются видимыми в любом режиме. Иначе неразобранное исчезло бы из интерфейса вместе со способом это починить.
Модалка «Новый клиент»
Открывается зелёной кнопкой над деревом. Что важно знать при мульти-орг:
- Организация выбирается в шапке модалки — селектом, когда в навбаре стоит «Все организации». Выбор здесь главнее папки и значения по умолчанию;
- Списки следуют за организацией: тарифы и NAS чужих компаний скрыты. Если у компании нет тарифов нужного типа, вместо пустого списка — пояснение (например, у организации все тарифы для юр.лиц, а создаётся физлицо);
- NAS показываются все, если своих у компании нет — оборудование обычно общее на сеть, и жёсткий фильтр просто не дал бы создать клиента;
- Подсказка «Попадёт в папку» под тарифом появляется, если у тарифа задана директория.
Раскладка новых клиентов
Куда попадёт клиент при создании, задаёт настройка
Настройки → Система → Профиль → «Раскладка новых клиентов»
(ABONENT_FOLDER_ROUTING):
| Режим | Поведение |
|---|---|
| По директории тарифа, иначе по организации | значение по умолчанию: сначала папка тарифа, при её отсутствии — папка организации |
| Только по папке организации | директория тарифа игнорируется |
| Не раскладывать | клиент остаётся там, где его создают |
flowchart TD
ADD["Создание клиента"] --> WHERE{"Откуда добавляют"}
WHERE -->|"из конкретной папки"| KEEP["Остаётся в ней"]
WHERE -->|"из корневой Все"| MODE{"Режим раскладки"}
MODE -->|"Не раскладывать"| ROOT["Остаётся в корне"]
MODE -->|"Только организация"| ORG["Папка организации"]
MODE -->|"По тарифу"| HAS{"У тарифа есть директория"}
HAS -->|"да"| TAR["Папка тарифа"]
HAS -->|"нет"| ORG
Если папка тарифа принадлежит другой компании, организация клиента синхронизируется по папке: иначе он лежал бы в чужой папке со своей меткой и пропадал из неё при фильтре по организации. На практике случай редкий — тарифы уже отфильтрованы по организации, и чужой выбрать нельзя.
Глобальный поиск
Глобальный поиск (Клиенты → 🔍 Глобальный поиск,
URL /admin/Abonents/search/) — плоский список всех клиентов
без иерархии папок, с расширенными фильтрами и массовыми операциями.
Папки в выборку не попадают (is_folder=false).
Раздел рассчитан на сценарии, когда нужно работать со всей базой сразу: найти клиента по обрывку информации (часть телефона, дома, фамилии), сделать массовую рассылку по тарифу, выгрузить срез для бухгалтерии.
Содержание раздела
KPI-бейджи в шапке
Над таблицей выводится 4 счётчика по результатам текущей выборки:
Активных (enabled=true) /
Сейчас онлайн (RADIUS-сессия < 15 мин) /
Выкл (enabled=false) /
Всего. Цифры пересчитываются на каждой смене фильтра.
Чипы быстрых фильтров (7 штук)
Чипы взаимоисключающие — активен может быть только один. Кликом по уже
активному чипу фильтр сбрасывается. Состояние записывается в URL
(?chip=debtors) — ссылку можно отправить коллеге.
| Чип | Иконка | Что показывает | SQL-условие |
|---|---|---|---|
| Должники | ⚠ | Клиенты с отрицательным балансом счёта | admin_account.ostatok < 0 |
| Бизнес | 💼 | Юридические лица | abonents.company = true |
| Без email | ✉ | Не указан e-mail для уведомлений | email IS NULL OR email = '' |
| Без телефона | 📵 | Не указан номер телефона (SMS не уйдут) | sms IS NULL OR sms = '' |
| Онлайн | 📡 | Сейчас в RADIUS-сессии (< 15 минут с last update) | USERS_RADIUSAUTH.LOGGED = 1 AND RADIUS_UPDATE >= now() - 15 min |
| Заблокированные | 🚫 | Любая активная блокировка (admin / negbal / own) | есть запись в ABONENTS_BLOCK с активным флагом |
| Обещ. платёж | 🤝 | Активен обещанный платёж (Usluga 30353) | users_usluga.usluga_id = 30353 AND enabled = true |
Multi-select фильтры
- Тариф — checkbox-список с поиском по названию.
Можно выбрать несколько тарифов одновременно. URL:
?tarif=131&tarif=43. Применение через кнопку «Применить», сброс — «Очистить». - Точка подключения — checkbox-список ConnectionPoints
(NAS-узлов и магистралей). URL:
?cp=Узел 1&cp=Узел 2. Полезно для оператора-инсталлятора, обслуживающего несколько домов / улиц / районов.
Текстовый поиск
Поле в шапке таблицы. Серверный поиск по: ФИО, номер договора, e-mail, телефон, адрес (город / улица / дом / квартира). Минимум 2 символа, debounce 300 мс. Шорткат Ctrl+K ставит фокус в поле поиска из любого места страницы.
Колонки таблицы (12)
- ☑ — чекбокс выбора для bulk-операций.
- № — внутренний ID клиента.
- ФИО — имя, кликабельно (открывает карточку).
- Дог. — номер договора.
- Тариф — название текущего тарифа; для бизнес-тарифов иконка 💼.
- Адрес — короткий формат через фильтр
home_short(ВО → Волгоградская обл., Влг → Волгоград, район сокращённо). - Точка подкл. — название ConnectionPoint.
- Контакты — иконки ✉ / 📞 с tooltip-значениями. Серая иконка = не заполнено.
- Баланс — баланс счёта; рядом 🤝 при активном ОП.
- Подкл. — дата подключения.
- 📡 — флаг «онлайн сейчас» (RADIUS-сессия < 15 мин).
- Статус — цветной бейдж (Активен / Заблок. / Удалён).
Подсветка строк — та же, что в папке: розовый фон — блокировка за неуплату, жёлтый — другая блокировка, красный — выключен, усиленный красноватый — долг более 500 ₽. Разбор классов — в описании папки.
Бесконечный скролл
Глобальный поиск не использует пагинацию: при прокрутке вниз подгружается
следующая порция результатов. Папочные страницы (/admin/Abonents/<id>/)
работают по тому же принципу через AJAX-endpoint
/admin/Abonents/<id>/children_json/ по 100 записей за запрос.
Массовые действия — 11 операций
Выбираете клиентов галочками в первой колонке (или «Выбрать все»
в шапке) — в шапке появляется выпадающее меню «Действия (N)»
с дюжиной операций. Endpoint: POST /admin/Abonents/bulk_action/
(payload: action + ids[] + параметры).
| Действие | Что делает | Undo |
|---|---|---|
Включить (enable) |
Снимает флаг enabled=false, шлёт CoA-Disconnect для переподключения. |
✅ |
Выключить (disable) |
Ставит enabled=false, разрывает RADIUS-сессии. |
✅ |
Удалить (delete) |
Soft-delete: deleted=true, все Users отключаются, CoA-Disconnect. |
✅ |
Изменить тариф (change_tarif) |
Смена тарифа в окне выбора. Срабатывает сразу, не «с 1-го числа». | ✅ |
Изменить точку подключения (change_cp) |
Перевешивает клиента на другую ConnectionPoint. | — |
Перенести в папку (move_folder) |
Меняет parent_id в MPTT-дереве (выбор папки из списка). |
✅ |
Добавить услугу (add_service) |
Подключает разовую/периодическую услугу (услуга из списка). | — |
Добавить бонус (add_bonus) |
Подключает бонус (компенсация / скидка с отрицательной суммой). | — |
Добавить операцию (add_finop) |
Создаёт FinanceOperations на счёт (тип, сумма, описание). |
— |
Добавить лояльность (add_loyalty) |
Включает программу лояльности (AbonentsLoyalty). |
— |
Отправить сообщение (send_msg) |
Рассылка по выбранным каналам: e-mail / SMS / Push / Telegram. | — |
Для destructive-операций (Включить / Выключить / Удалить / Изменить тариф /
Перенести в папку) после выполнения в шапке появляется баннер
«Откатить (доступно N мин)» с таймером 1 час. Клик отменяет
изменение для всех затронутых клиентов через
POST /admin/Abonents/bulk_undo/. Undo-токен хранится в
sessionStorage браузера. Create-операции (Добавить услугу / бонус /
финоперацию / лояльность / отправить сообщение) не откатываются — придётся
вручную удалять созданные записи.
Подтверждение опасных операций
Удаление, отключение, включение и смена тарифа открывают модальное окно подтверждения: показывает превью первых 5 имён выбранных клиентов, общее их количество и требует ввести точное число выбранных записей для активации кнопки «Подтвердить». Это защита от случайной массовой операции по невнимательности.
CSV-экспорт
Кнопка «📥 CSV» в шапке выгружает текущий видимый срез
(/admin/Abonents/search/export/): UTF-8 BOM, разделитель «;»
для Excel RU. Колонки те же что в таблице плюс расшифровка тарифа,
оператор и баланс в рублях.
Слайд-панель клиента (drawer)
В строках таблиц списков клиентов (папка, глобальный поиск, должники) у каждой записи справа есть кнопка ℹ Подробнее (на mobile — клик по имени). Клик открывает выезжающую справа панель (slide-panel / drawer) с краткой сводкой по клиенту — без ухода со списка и потери контекста (фильтры, прокрутка, выделение сохраняются).
Панель решает задачу «быстро глянуть и сделать», когда полная карточка с десятком вкладок избыточна. Типичные сценарии за 5–30 секунд: проверить баланс и активировать ОП, посмотреть статус сессии при жалобе на интернет, отправить SMS-напоминание, добавить комментарий-заметку для коллег.
Содержание раздела
- Где доступна панель
- Шапка с плашками статуса
- Финансовый KPI-блок
- Аккордеон разделов и smart-defaults
- Сброс сессии из блока «Последняя сессия»
- Раздел «Поддержка» (HelpDesk)
- Sticky footer — отправка сообщений
- Каналы отправки (SMS/Email/Telegram/ЛК/ВК/Push)
- Email в фирменном брендинге
- Доступность (WCAG colour independence)
- Mobile-режим
Где доступна панель
- Папка клиентов (
/admin/Abonents/<id>/) — кнопка ℹ в каждой строке. - Глобальный поиск (
/admin/Abonents/search/) — кнопка ℹ после колонки «Статус». - Должники (
/admin/debtors/) — последняя колонка таблицы. - На mobile — клик по имени клиента в любом списке (
.abd-name-link).
Программный API из любой страницы: window.AbDrawer.open(abonentId).
Шапка с плашками статуса
Вверху панели — компактные плашки с ключевыми атрибутами клиента (видно с одного взгляда, без скролла):
| Плашка | Что показывает | Цвет |
|---|---|---|
| #<номер договора> | Номер договора клиента | — |
| Активен / Отключён | Состояние клиента целиком | зелёный / жёлтый |
| Физ. лицо / Бизнес | Тип клиента | серый |
| 🔑 Логин | Основной логин учётной записи (скрыт на mobile) | серый |
| 🌐 IP | IP-адрес учётной записи | серый |
| 🔓 Вкл. / 🔒 Выкл. | Состояние учётной записи (отдельно от клиента) | зелёный / жёлтый |
| 📶 Онлайн / 🔌 Офлайн | Сейчас в RADIUS-сессии или нет | зелёный / жёлтый |
| 🪪 СОРМ N/9 | Полнота персональных данных по СОРМ-требованиям | зелёный (9/9) / жёлтый (5–8) / красный (0–4) |
Шапка имеет фон заметно темнее тела (light #e8edf2 / dark
#14171b) — оператор сразу различает «где статус, где данные».
Финансовый KPI-блок
Сразу под шапкой — KPI-сетка 2×2 с самыми важными цифрами:
- Баланс — с цветным акцентом (зелёный для +, красный для −).
- Дней долга или Хватит на N дней — label
меняется в зависимости от знака баланса. При плюсе показываем
«Хватит на N дней» =
balance / стоимость_тарифа × 30. Цвет: зелёный ≥30 дней, нейтральный 7–29, красный <7 дней (нужен платёж). - Тариф — название текущего тарифа (truncate с tooltip).
- Посл. платёж — дата последнего входящего платежа.
Если активен обещанный платёж — отдельная плашка с суммой и датой окончания под KPI-сеткой.
Аккордеон разделов и smart-defaults
Разделы панели объединены в аккордеон — оператор сам
открывает что нужно, не скроллит мимо. Состояние свёрнутости запоминается
в localStorage отдельно по каждой секции — выбор оператора сохраняется.
| Раздел | Smart-default (открыт если…) |
|---|---|
| 👤 Учётные записи | Учётка отключена (!primary_enabled) — оператор сразу видит проблему |
| 🌐 Последняя сессия | Клиент онлайн или есть активная сессия |
| 📈 Платежи за 12 мес. | Всегда свёрнут — открывается по запросу |
| 📋 Последние операции | Должник (balance < 0) — оператор смотрит откуда долг |
| 🏛 Банк | Раздел показывается только при наличии хотя бы одного платежа из банковских выписок; открывается смотря по контексту вызова |
| 📝 Комментарии операторов | Уже есть комментарии — значит история важна |
| 🎧 Поддержка | Клиент ждёт ответа в HelpDesk (waiting_for_us > 0) |
Раздел «Банк» показывает последние платежи клиента из банковских выписок (банк, дата, сумма, плательщик). При открытии панели кликом по имени клиента в очереди чеков («Очередь чеков») панель автоматически прокручивается к разделу «Банк» и раскрывает его — оператор сразу видит, откуда пришёл платёж по разбираемой операции.
Сброс сессии из блока «Последняя сессия»
Если у клиента есть активная RADIUS-сессия, в блоке
«Последняя сессия» рядом с бейджем «Активная сессия» появляется
кнопка «🔄 Сброс сессии». Она нужна при зависшей сессии
(NAS не прислал Accounting-Stop, клиент «висит онлайн», но интернета нет).
- Клик открывает модалку подтверждения: «Сбросить активную сессию? Сессия будет завершена, абонент переподключится. Использовать при зависшей сессии.» — с кнопками Отмена / Сбросить.
- Подтверждение шлёт
POST /admin/users_api/<user_id>/reset_session/: сбрасываетUSERS_RADIUSAUTH.LOGGED, закрывает открытыеRADIUS_SESSIONSи рассылает CoA Disconnect на NAS — клиент переавторизуется. - Действие записывается в аудит (
AuditOperations).
Если оператор однажды раскрыл/свернул секцию вручную — это его выбор сохраняется
в localStorage и smart-defaults больше не перетирают.
Раздел «Поддержка» (HelpDesk)
Шестой раздел аккордеона — интеграция с HelpDesk. Биллинг показывает все обращения клиента в техподдержку прямо в слайд-панели, без перехода в HelpDesk:
- Pill-счётчик в заголовке: «Поддержка 3»
показывает количество открытых тикетов. Если есть тикеты, ожидающие
ответа от нас (
customerWaitingSinceиз FS), счётчик становится красным с pulse-анимацией — оператор сразу видит «горящие» обращения. - Summary-stats внутри: «Ждёт ответа N · Открыто M · Ожидание K · Закрыто L».
- До 5 свежих тикетов карточками: # · статус · «ждёт N часов» бейдж → клик открывает обращение в HelpDesk (новая вкладка).
- Smart-default: раздел открыт если
waiting_for_us > 0. - Резильентность: если HelpDesk недоступен — раздел просто пуст, ошибки в drawer не пробрасываются.
Sticky footer — форма отправки всегда видна
Форма отправки сообщений и табы каналов прилеплены ко дну панели (не уезжают при скролле истории сообщений). Оператор не скроллит чтобы написать SMS — форма всегда на виду, как в современных мессенджерах.
Каналы отправки (SMS / Email / Telegram / ЛК / ВК / Push)
Над формой ввода — chip-табы с каналами отправки. Показываются только доступные клиенту каналы:
- SMS — если у клиента указан телефон.
- Email — если указан email.
- Telegram — если привязан Telegram-чат (
telegram_chat_id). - ЛК — всегда доступен (push в личный кабинет через MsgStack).
- ВК — если привязан VK-аккаунт.
- Push — если установлено мобильное приложение и есть push-токен.
При выборе таба Email над формой появляется поле «Тема письма» — если оператор не укажет, дефолтная тема возьмётся из первой строки текста (до 80 символов).
SMS-форма поддерживает дефолтный шаблон: при пустом тексте у должника автоматически отправляется напоминание «Договор XXX. Задолженность YYY ₽. После пополнения счёта доступ к услугам восстановится. Узнать баланс или взять ОП — отправьте «Баланс» или «Оп» в ответ на это сообщение. ЛК: lk.вашдомен.ru».
Для клиентов с положительным балансом дефолтный шаблон не отправляется —
backend возвращает ошибку no_text_positive_balance, оператор должен
ввести текст вручную.
Email в фирменном брендинге
Email-сообщения отправляются HTML в фирменном стиле — не plain text, а полноценное письмо с шаблоном emails/generic.html:
- Header: brand-color
#43b77a, имя оператора, дата. - Персональное приветствие: «Здравствуйте, <Имя>!» —
имя берётся из ФИО клиента (
parts[1]для физ.лиц, generic для юр.лиц). - Тело письма с текстом оператора (форматирование переносов).
- Footer: реквизиты компании, телефон поддержки, договор,
баланс — из
_build_msg_email_context. - Без CTA-кнопки «Открыть личный кабинет» (
hide_cta=True) — она не нужна для ручных сообщений оператора, только для авто-уведомлений.
Авто-детект шаблона по теме/тексту: если содержит «обещанн», «оплат», «блокир»,
«возобновл», «баланс+низк» — выбирается специализированный пресет
(promise_pay/payment_received/blocked/
unblocked/low_balance), иначе generic.
Доступность (WCAG colour independence)
Все цветовые статусы дублируются иконками — пользователи с deuteranopia (8% мужчин) различают информацию без полагания на цвет:
- FinOps: знак
+/−+ иконка↑/↓перед суммой. - Учётные записи:
fa-check-circle(online),fa-circleoutline (offline),fa-times-circle(disabled). - Сессия:
fa-play-circle(active) /fa-stop-circle(closed). - SMS-статусы: цветные плашки «доставлен» (зелёный), «отправл.» (синий), «ожидание» (серый), «ошибка» (красный).
Mobile-режим
На телефоне (≤768px) панель раскрывается на весь экран
(width: 100vw, height: 100dvh, без скруглений и отступа
сверху) — не узкая шторка сбоку и не нижний лист, а полноэкранная слайд-панель,
как модалки в мобильной версии проекта. Поведение одинаковое с desktop, только:
- Login-бейдж в шапке скрыт (длинный логин ломает строку).
- Textarea формы отправки одной строки (
height: 38px) сfont-size: 16px— iOS Safari не зумит при фокусе. - Sticky footer с safe-area padding (iPhone home indicator).
Тёмная тема
Drawer полностью адаптирован под тёмную тему. Header ещё темнее тела
(#14171b vs body #1f2227), все секции и формы
имеют тёмные варианты, footer-actions с своим bg.
Карточка клиента
Переход кликом по строке клиента — открывается отдельная страница с вкладками Информация, История, Тариф и услуги, Бонусы, Финансы, Точки подключения, Учётные записи, RADIUS, СОРМ, Реквизиты, HelpDesk, Лояльность. Подробное описание каждой вкладки — в отдельном разделе Карточка клиента.
Bulk-операции и Undo
Выберите клиентов галочками — появится плавающая панель действий внизу экрана.
Доступные действия:
- Включить / Выключить / Удалить (с подтверждением и превью имён).
- Сменить тариф / точку подключения / папку.
- Подключить услугу / бонус / финоперацию / программу лояльности.
- Отправить сообщение (e-mail / SMS / Push / Telegram).
Для destructive-операций (delete, disable, enable, change_tarif, move_folder) после выполнения в шапке появляется баннер «Откатить (доступно N мин)» с таймером 1 час. Клик отменяет изменение для всех затронутых клиентов.
На карте
Карта клиентов в браузере (Leaflet + OSM / 2ГИС / Yandex).
Каждый маркер — клиент; отдельно показываются NAS и
слой заявок Поддержки. URL:
/admin/abonents/map/. Меню сайдбара: Клиенты → 🗺 На карте.
- "На карте"
- Слои
- Клиенты
- Кластеры по плотности
- Цвет = статус
- NAS
- Покрытие
- Онлайн-панель
- Заявки Поддержки
- Фильтры
- Статусы
- Услуги
- Поиск Ctrl+K
- Данные
- Только точные координаты
- Скоуп по организации
- Кеш 5 минут
- Действия
- Попап клиента
- Карточка заявки
- Правка координат
Возможности
- Поиск с автокомплитом в три секции: Адреса, Клиенты, Оборудование. Шорткат Ctrl+K (или ⌘+K на Mac) ставит фокус в поле, не открывая поиск navbar. Минимум 2 символа, debounce 300 мс. Клавиатура: ↑↓ Enter Esc.
- Фильтр статусов (Активные / Заблокированные / Приостановленные) и фильтр услуг (Интернет / Видео / IPTV / Телефония / ЧОП) — выпадающие селекторы с toggle. Минимум один статус выбран всегда; дефолт — Активные + Интернет.
- Кластеры — на низком zoom маркеры группируются. Свёртываемая легенда (правый-нижний угол) объясняет цвета и размеры кластеров (5 / 25 / 120 точек) и статусов.
- NAS на карте — включённые серверы доступа показываются по координатам адреса. Popup NAS: бейджи PROD / CoA / SSH, состояние UP/DOWN, прогресс-бар «онлайн / всего», кнопки «Клиенты» (открывает сайдбар, см. ниже) и «Связи» (покрытие). Инфраструктура (Switch / узлы / трассы) — на отдельной карте сети.
- Слой «Заявки» — тикеты Поддержки СмИТ Биллинг с геопривязкой по адресу клиента. Включён при загрузке; клик по маркеру открывает карточку тикета.
- Popup клиента (см. скриншот ниже): Договор + лейбл организации (при multi-org), NAS ссылкой (→ сайдбар клиентов NAS), тариф, адрес, баланс, иконки услуг. Клик по ФИО открывает карточку клиента с прокруткой к блоку «Услуги»; «Позвонить» / «E-mail» — карточку с прокруткой к блоку сообщений.
- Сохранение viewport в браузере
(
localStorage.map_last_view, TTL 7 дней) + shareable URL?center=lat,lon&zoom=N— карта помнит положение, ссылку можно отправить коллеге. - Тёмная тема — карта становится тёмной вместе с интерфейсом: подписи остаются русскими, маркеры сохраняют цвета, попапы и сайдбары адаптированы под тёмный фон. Адаптив: на мобильном поиск — отдельной строкой, фильтры — в bottom-sheet по кнопке «Фильтры».
Откуда берутся точки
Карта не запрашивает данные при каждом движении — весь набор приходит одним JSON и живёт в памяти вкладки. Отсюда два следствия: панорамирование мгновенное, но состав точек фиксируется в момент загрузки.
flowchart LR
REQ["Открытие карты"] --> CACHE{"Кеш 5 минут
по организации"}
CACHE -->|"есть"| JSON["JSON одним ответом"]
CACHE -->|"нет"| HOMES["Адреса с координатами
qc_geo пусто или ≤ 1"]
HOMES --> SCOPE["Скоуп по организации
клиента"]
SCOPE --> BATCH["Батч-запросы:
NAS, блокировки, услуги"]
BATCH --> JSON
JSON --> RENDER["Кластеры на карте"]
qc_geo ≤ 1 — «точно дом» или «улица». Адреса, которые геокодер
свёл к центру города (qc_geo ≥ 2), исключены: иначе сотни
клиентов слиплись бы в одну точку и карта потеряла бы смысл.
Мультиорганизация
Точки, оборудование и заявки скоупятся по организации клиента. Важная деталь реализации: кеш партиционирован — ключ включает выбранную в шапке организацию и список доступных сотруднику компаний. Без этого первый открывший карту наполнил бы общий кеш своими данными, и остальные увидели бы чужих клиентов.
Если у организации нет ни одного адреса с точными координатами, карта не молчит, а объясняет причину и ведёт в редактор координат:
Сайдбар «Клиенты на NAS»
Клик по NAS-маркеру (кнопка «Клиенты») или по NAS-ссылке в попапе клиента открывает слайд-панель справа со списком клиентов выбранного NAS. Фильтр-чипы Все / Online / Offline со счётчиками, поиск (ФИО / логин / договор / IP), бесконечная прокрутка. Каждая строка — кликабельна (→ карточка клиента) и показывает: ФИО, бейдж «Заблок.» при блокировке, логин·IP, тариф, баланс (цветной ↑/↓) и иконки услуг.
Поток: маркер → попап → карточка / сайдбар
flowchart LR
DATA["/admin/abonents/map/data/
points + NAS + заявки"] --> MAP["Карта Leaflet
кластеры"]
MAP --> ABM(("Маркер
клиента"))
MAP --> NASM(("Маркер
NAS"))
MAP --> TKT(("Маркер
заявки"))
ABM --> POP["Popup клиента
org · NAS · услуги"]
POP -->|"ФИО"| DRAWER["Карточка клиента
→ блок «Услуги»"]
POP -->|"Позвонить / E-mail"| DRAWERM["Карточка клиента
→ сообщения"]
POP -->|"NAS-ссылка"| PANEL["Сайдбар «Клиенты NAS»
Все / Online / Offline"]
NASM -->|"«Клиенты»"| PANEL
PANEL -->|"строка"| DRAWER
TKT --> TICKET["Карточка тикета
Поддержка СмИТ"]
style DATA fill:#1e3a5f,stroke:#5BA89D,color:#fff
style MAP fill:#134e4a,stroke:#43b77a,color:#fff
style POP fill:#1e3a5f,stroke:#5BA89D,color:#fff
style PANEL fill:#43b77a,stroke:#2d9a5f,color:#fff
style DRAWER fill:#43b77a,stroke:#2d9a5f,color:#fff
style DRAWERM fill:#14532d,stroke:#16a34a,color:#fff
style TICKET fill:#78350f,stroke:#d97706,color:#fff
Тёмная тема и мобильный вид
Попап и сайдбар NAS полностью адаптированы под тёмную тему (контраст лейблов/значений ≥ WCAG AA). На мобильном панель фильтров — bottom-sheet.
Попап клиента
Клик по маркеру открывает карточку: договор, организация, NAS, тариф, адрес, баланс, точка подключения и значки подключённых услуг. Кнопки ведут в карточку клиента, звонок и почту — из карты можно начать работу, не открывая список.
Маркеры заявок открывают карточку тикета Поддержки в правой панели: история обращений, расшифровка звонка, приоритет и поле ответа. Снимки из переписки листаются просмотрщиком прямо в панели, у файлов виден значок своего формата, а прошлые обращения клиента открываются здесь же — со стрелкой возврата. Панель общая с картой сети, подробности там.
Вес карты и скорость
Карта — самый тяжёлый JSON в системе: несколько тысяч точек одним ответом. Две правки изменили порядок величины:
| Что | Было | Стало |
|---|---|---|
| Передача по сети | 4585 КБ | 380 КБ |
| Размер JSON | 4584 КБ | 2750 КБ |
| Загрузка страницы | 11,6 с | 2,6 с |
- gzip для JSON в nginx — директива
gzip_typesбыла закомментирована, поэтому сжимался только HTML. Сжатие включено для всех JSON-ответов биллинга, не только карты; - Ответ без экранирования кириллицы — по умолчанию Django пишет русские буквы escape-последовательностями по 6 байт на символ, из-за чего 2,7 МБ данных превращались в 4,6 МБ.
Геокодирование адресов
Чтобы клиент появился на карте, его адрес (Homes.parent_id)
должен иметь координаты lat и lon. Заполнение
координат происходит автоматически через геокодеры —
внешние сервисы, которые по строке адреса возвращают координаты.
Поддерживаются два провайдера:
| Провайдер | Где настроить | Лимиты | Команда |
|---|---|---|---|
| DaData (рекомендуется для России) | /admin/settings/integrations/?tab=geo →
«Адреса», поле DADATA_TOKEN. |
10 000 запросов/день бесплатно (тариф «Бесплатный»), но не более 10 тыс. в месяц. | manage.py geocode_homes_dadata [--re-geocode] |
| Яндекс.Геокодер (запасной) | /admin/settings/integrations/?tab=geo →
«Адреса», поле YANDEX_GEOCODER_API_KEY. |
25 000 запросов/день бесплатно (HTTP Геокодер). | manage.py geocode_homes_yandex [--re-geocode] [--only-no-qc] |
API-токены задаются в разделе «Настройки → Интеграции → География» (вкладка «География → Адреса»). Здесь же есть кнопка «Проверить» для теста подключения и счётчик геокодированных адресов.
ADDRESS_ADMIN_GROUPS
(по умолчанию root, address). Остальным сотрудникам
сохранение вернёт 403.
Запуск геокодирования
На рабочий сервер сейчас геокодировано ~95% адресов (4045 из 4261), из активных клиентов ~90% на карте. Чтобы геокодировать новый адрес автоматически — введите его в форме клиента, и при следующем прогоне команды (или nightly Celery-задаче) он попадёт на карту. Запустить геокодирование вручную:
docker compose exec web python manage.py geocode_homes_dadata
# или принудительно для всех (включая уже геокодированные):
docker compose exec web python manage.py geocode_homes_dadata --re-geocode
Команда:
- Берёт все
Homesгдеlat IS NULL OR lon IS NULL(или все, если--re-geocode). - Обращается к DaData suggest API
(
https://suggestions.dadata.ru/.../suggest/address) с полным адресом из иерархии Homes (регион → город → улица → дом). - Записывает координаты
geo_lat/geo_lonи полеqc_geo(precision: 0=точно дом, 1=улица, 2=город — чем меньше, тем точнее). - Делает паузу между запросами (по умолчанию 0.1 сек) чтобы не превысить лимит сервиса.
Маркеры клиентов на адресах с qc_geo=2 (только город) на
карте отображаются как «приблизительные» — несколько клиентов одного
города могут лечь на одну точку. Для повышения точности проверьте, что
у адреса заполнены поля «Улица» и «Дом».
Reverse-геокодинг (заполнение города/района по координатам)
Если у адреса есть координаты, но не заполнен город или район
(после миграции из старых биллингов это бывает), используйте команду
manage.py enrich_homes — она через Яндекс.Геокодер
(обратное направление: координаты → адрес) восстанавливает
текстовые поля.
Тайловый слой (фон карты)
Подложка выбирается на той же вкладке «География» и применяется сразу ко
всем картам панели: OpenStreetMap (по умолчанию, без ключа), CARTO (ключ выдаёт
сервер лицензий), 2ГИС и Яндекс.Карты (свой ключ). В тёмной теме интерфейса карта
тоже становится тёмной, а если провайдер перестал отдавать тайлы — подложка сама
переключается на OpenStreetMap, чтобы карта не осталась пустой. Сотрудник может
выбрать провайдера лично в Профиле. Подробнее —
Подложка карт: провайдер, тёмная тема,
ключ CARTO. Стиль карточки в
/admin/settings/integrations/?tab=geo
показывает количество геокодированных адресов и точность (% домов
с qc_geo=0).