Добавлена офлайн/EndUser-подпись «ознакомлен и принял в работу» для документов отчётов и файловые вложения к задачам через Nextcloud.
Co-authored-by: Cursor <cursoragent@cursor.com>
Вынесены can_* для кассы и посещаемости, синхронизация в builders/signals, seed групп в core.0023–0024.
Co-authored-by: Cursor <cursoragent@cursor.com>
В /documents/ добавлено дерево департаментов, права view/edit через DocumentAccessGrant и выдача доступа сотрудникам/отделам/группам.
Co-authored-by: Cursor <cursoragent@cursor.com>
Сотрудник может подключить несколько IMAP-ящиков и переключаться между ними.
Дислокация снова показывает всю сеть, а не только назначенные аптеки.
Co-authored-by: Cursor <cursoragent@cursor.com>
Раньше любой сбой get_or_create обнулял ID и шёл в create. Теперь
привязка через _attach_mattermost_profile, поиск по username, а при
«email уже есть» — линк существующей учётки.
Co-authored-by: Cursor <cursoragent@cursor.com>
Убрана пустая «графік»-вкладка; Mattermost/AD сгруппированы с подсказкой
про ручную привязку; группы через filter_horizontal.
Co-authored-by: Cursor <cursoragent@cursor.com>
Браузер больше не ходит на chat.* с portal.* — REST идёт через Django с PAT;
WS backoff без бесконечных 403; utilities.js ждёт DOMContentLoaded.
Co-authored-by: Cursor <cursoragent@cursor.com>
Сотрудник сам подключает ящик (autoconfig, вложения, пагинация списка, надёжный IMAP-поиск); в хедере работает /api/search по разделам и сущностям с ACL. Также устойчивость Mattermost-токена для чата.
Co-authored-by: Cursor <cursoragent@cursor.com>
Каждому виджету можно задать ширину: «Полная» (на всю строку) или «½».
Виджеты выстраиваются в flex-сетку: два «½» встают в один ряд, «Полная»
занимает строку целиком. На узких экранах половинные переносятся в столбик.
- DashboardLayout.widget_widths (JSON {ключ: half|full}) + миграция 0022;
поле widgets не тронуто — обратная совместимость сохранена
- core/dashboard: layout_widths, ширина в build_dashboard_context/
customize_rows, save_layout(widths=...), normalize_width
- dashboard.html: flex-wrap сетка по ширине; customize: переключатель ½/полная
- tests: 6 (сохранение/чтение ширин, нормализация, дефолт, контекст, POST)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Единый стиль портала (логотип, тема, кнопки) через error_base.html:
- 404 — страница не найдена
- 403 — доступ запрещён (+ ссылка на IT-поддержку)
- 500 — внутренняя ошибка (автономна: без request/context-процессоров)
- 503 — технические работы
MaintenanceModeMiddleware: при MAINTENANCE_MODE=True отдаёт 503 всем, кроме
суперпользователей и путей входа/админки/статики (чтобы админ мог войти и
выключить режим). По умолчанию выключен — поведение не меняется.
- tests: рендер всех шаблонов, кастомный 404, режим техработ вкл/выкл/супер
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Management-команда convert_usernames приводит логины существующих
сотрудников к новому формату <буква имени>.<фамилия> (та же логика
_username_base, что при найме).
- dry-run по умолчанию, запись только с --apply
- суперпользователи пропускаются (не переименовать свой админ-аккаунт),
переопределяется через --include-superusers
- --exclude a.user,mail@dom — исключить по логину/email
- уникальность: коллизии получают суффикс; исключённые и уже корректные
логины резервируются первыми
- tests: 8 (dry-run, apply, коллизии, skip/ include суперов, exclude, резерв)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Админ-страница «Ссылки на установку пароля» (Сотрудники → действия):
таблица всех активных сотрудников со статусом (пароль установлен / MM
привязан / активная ссылка), кнопки «создать всем без пароля», создать/
обновить/отозвать по одному, экспорт CSV. Ссылки одноразовые, TTL 30 дней.
При активации по ссылке, помимо пароля портала, теперь синхронно ставится
тот же пароль в Mattermost: аккаунт связывается по email/ID или создаётся
(первичная связь). Ошибка MM не отменяет установку пароля портала.
- services/password_invite: bulk_create_invites_for_pending,
active_invites_by_employee, revoke_active_invites (BULK_TTL_DAYS=30)
- mattermost sync_service: link_and_set_password (ok|disabled|error)
- views_password: MM-синк после commit (вне транзакции, best-effort)
- EmployeeAdmin: action password-invites + шаблон
- tests: 6 новых (bulk, revoke, MM-линк ok/disabled, активация, admin+CSV)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Тесты больше не хардкодят /admin/ — путь берётся из reverse (admin:index /
именованный admin-URL), поэтому переименование префикса админки их не ломает.
Плюс подчинённому в EmployeeDetailTests задан email, не содержащий логин —
иначе проверка «логин скрыт от руководителя» ложно срабатывала на подстроку.
- apps/employees/tests: reverse('admin:employees_employee_change')
- core/tests_user_groups: matrix_url через reverse('admin:index')
- core/tests: distinct email у подчинённого
Полный прогон: 560 тестов, 0 провалов.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Делает HEAD целостным: коммит 5366799 уже импортировал эти файлы, но они
не были добавлены в git (свежий checkout падал с ImportError, а migrate не
создавал таблицу GroupLocationAccess).
- core/location_access.py: область данных (accessible_pharmacies/locations,
scoped_queryset/employees, has_all_locations) — fail-closed
- apps/locations/models/access.py + миграция 0014: GroupLocationAccess,
право locations.view_all_locations
- core/tests_location_access.py: 15 сценариев scoping
- apps/reports/*: снятие лишнего гейта has_all_locations с IT-отчёта отдела
(руководитель не обязан иметь доступ ко всей сети) + тест
- apps/rro/tests: чужая аптека теперь 404 (единая область доступа), а не
«200 с урезанными панелями» — под новую модель
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Виджеты теперь могут требовать доступ к разделу портала (required_section).
Недоступный виджет не отдаётся и не предлагается в настройке — даже если
остался в сохранённой раскладке или в наборе по умолчанию; save_layout
отбрасывает недоступные ключи (защита от подмены). Виджет «Метрики»
(общекорпоративная сводка) требует доступа к разделу «Отчёты».
Плитки в «Быстром доступе» скрываются по can_section (Документы, Задачи,
Календарь, Сотрудники) — как email и справочники ранее; остаются только
разделы, доступные пользователю.
- core/dashboard.py: required_section, widget_allowed, accessible_keys;
фильтрация в resolve/customize/save
- quick_access.html: плитки под can_section
- tests: 6 тестов доступности (скрытие/показ, отбрасывание, ре-фильтр)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Три новых виджета к кастомизируемому дашборду:
- upcoming — праздники, отпуска, ДР, смены и встречи на 2 недели вперёд;
переиспользует сборщики календаря (с их логикой видимости) через новый
calendar_service.upcoming_events()
- my_hr_requests — статусы моих кадровых заявок (отпуск/больничный/отгул)
- my_tickets — мои открытые обращения в IT-поддержку
Все три — в реестре DASHBOARD_WIDGETS, доступны в настройке дашборда и
наборах по умолчанию. Тесты: реестр, контекст HR-заявок, рендер виджетов.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Дашборд теперь собирается из виджетов (приветствие, метрики, мои задачи,
новости, быстрый доступ). Сотрудник настраивает состав и порядок под себя
(вкл/выкл + перемещение ↑↓), а для тех, кто не настраивал, действует набор
по умолчанию по типу локации (Офис/Аптека/Склад), задаётся в админке.
- core/dashboard.py: реестр виджетов + сборка раскладки
- core/models/dashboard.py: DashboardLayout (персональная), DashboardDefaults
(синглтон по типу локации)
- core/views.py: dashboard_view через реестр + dashboard_customize_view
- templates/pages/dashboard_widgets/*: виджеты-партиалы; dashboard.html —
цикл по виджетам; dashboard_customize.html — настройка (HTMX + ↑↓)
- admin: DashboardDefaults (чекбоксами по локации) + read-only раскладки;
пункт меню «Дашборд по умолчанию»
- core/tests_dashboard.py: 11 тестов (логика раскладки + view)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ранее разделы с perm=None (Новости, Задачи, Дислокация, Документы, Чат,
Календарь, HR-портал, Справка) были доступны любому вошедшему — сотрудник
без назначенной группы видел почти весь портал. Теперь такие разделы
требуют хотя бы одной группы; без групп доступны только базовые:
Главная, Профиль, Поддержка IT.
- user_can_access: BASELINE_OPEN_KEYS + gate «есть ли группа» для perm=None
- PortalSectionAccessMiddleware: барьер по префиксу URL (в т.ч. вложенные
страницы вроде /tasks/5/), /settings/ не гейтим (редирект на свой профиль)
- index.html: пункты меню под can_section (сайдбар, дропдаун, bottom-nav),
секция «Управление» скрывается целиком, если пусто
- тесты: политика без-группы + прямой заход по URL; тест-фабрики выдают
группу (реальный сотрудник её имеет)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На карточке Employee — вкладка «Доступ (сводка)»: по каждому
ограниченному разделу портала показывает итог (есть/нет) и источник
(какая группа дала право, individual grant/revoke override, личное право).
Считается теми же правилами, что Employee.has_perm (override имеет
приоритет над группами), поэтому сводка 1-в-1 совпадает с реальной
проверкой доступа. Править по-прежнему: поле «Группы» + инлайн
«Доп. права на разделы портала» (grant/revoke).
- core/access_summary.py: employee_access_summary()
- EmployeeAdmin: readonly effective_access_summary + вкладка
- core/tests_access_summary.py: 6 тестов (в т.ч. совпадение с has_perm)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При утверждении найма сотрудник получает роли (группы) по умолчанию для
своего типа локации (Аптека/Офис/Склад). Наборы настраиваются в
EmployeeDefaults (админка «Настройки сотрудников по умолчанию»).
- EmployeeDefaults: 3 M2M default_groups_* + apply_default_groups();
- approve_hiring_request вызывает apply_default_groups после создания Employee;
- админка: блок «Роли по умолчанию при найме» (filter_horizontal);
- миграция 0023; тесты найма.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Иван Иванов -> i.ivanov (транслит pytils, нижний регистр, коллизия +счётчик).
Раньше slugify('Фамилия.Имя') терял точку и давал ivanovivan. Единый формат
в найме (_generate_username) и в импорте (transliterate_username).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кнопка «Матрица доступа» на списке групп (unfold actions_list) открывает
сетку: строки — разделы портала, колонки — все группы, галочки просмотра.
Сохранение через apply_full_access_matrix (CUD не трогает — они на карточке
группы). Логика переиспользована из user_groups_portal. Смоук-тесты рендера
и сохранения.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Две сущности (auth.Group + PermissionGroup-профиль) слиты в одну страницу
админки Group: имя, разделы портала (curated), прочие права, описание,
активна, ссылка на матрицу доступа. Отдельный пункт «Группы разрешений»
убран из меню; модель PermissionGroup остаётся профилем под капотом.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hx-post вне формы (кнопка «Синхронизировать чат») слал POST без CSRF —
Forbidden (CSRF token missing). Добавил hx-headers с X-CSRFToken на <body>:
токен уходит со всеми htmx-запросами, работает и при CSRF_COOKIE_HTTPONLY.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Если у бота задан только access-token (без id/username/имени), при сохранении
в админке дёргаем GET users/me этим токеном и заполняем user_id, username,
display_name автоматически.
- MattermostService.identity_for_token(token) — кто владелец токена;
- MattermostBotAdmin.save_model подставляет пустые поля, сообщает результат.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бот больше не привязан к единственной role (unique). Вместо неё три флага-
галочки: can_provisioning / can_channels / can_tasks — можно совмещать.
get_active(role) ищет активного бота с соответствующей галочкой.
- модель: убран role, добавлены 3 boolean, get_active/caps по флагам;
- миграция 0006: перенос старой role в галочки, затем удаление role;
- админка: чекбоксы «Возможности бота»;
- команда create_mattermost_bot: --role включает нужную галочку (username-based upsert).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
manage.py cleanup [--days N] [--dry-run] — удаляет устаревшие журналы
синхронизации (AD/Carbonio/Nextcloud/Mattermost), просроченные инвайты
паролей и истёкшие сессии. Бизнес-данные не трогает. Срок хранения —
settings.CLEANUP_LOG_RETENTION_DAYS (default 90, env-override). Запуск по cron.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- модель MattermostSyncLog (employee/action/status/message) + read-only админка;
- запись при добавлении в команды/каналы (успех и ошибка) в sync_service;
- пункт «Журнал Mattermost» в меню рядом с прочими журналами.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>