Commit Graph

146 Commits

Author SHA1 Message Date
lokses 7a149dd84d portal: отдельные виджеты PNMS/PVMS-прав в ролях и seed-миграции.
Вынесены can_* для кассы и посещаемости, синхронизация в builders/signals, seed групп в core.0023–0024.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 17:22:41 +03:00
lokses b441b4f781 fix(nextcloud): dependency 0002 на core.0022 вместо отсутствующего 0024.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 17:21:58 +03:00
lokses 5800f79eec documents: иерархия отделов и ACL на папки/файлы с people-picker.
В /documents/ добавлено дерево департаментов, права view/edit через DocumentAccessGrant и выдача доступа сотрудникам/отделам/группам.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 17:21:01 +03:00
lokses ad8730c752 i18n: локализация multi-mailbox UI и полей UserMailbox (uk/en)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 18:09:56 +03:00
lokses 9c8d0d109a mail: несколько ящиков с переключателем; дислокация без скоупа локаций
Сотрудник может подключить несколько IMAP-ящиков и переключаться между ними.
Дислокация снова показывает всю сеть, а не только назначенные аптеки.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 15:56:13 +03:00
lokses c6684721e7 mail: batched preview FETCH + одна IMAP-сессия на чтение письма
Убраны ~40 round-trip на список; unread через STATUS; открытие письма
без второго LOGIN.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 15:12:48 +03:00
lokses 9e1505296a nav: новая структура меню — Управление, Поддержка, отдельный блок IT
Справочники и Настройки в Управление; IT-отдел и AD — только при доступе.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 14:22:18 +03:00
lokses b0fb8c5218 rename: «Учётные данные» → «Операционный учёт» в меню и портале
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 14:14:31 +03:00
lokses ebe75aa834 fix: sync Mattermost — не создавать дубль при конфликте профиля/email
Раньше любой сбой get_or_create обнулял ID и шёл в create. Теперь
привязка через _attach_mattermost_profile, поиск по username, а при
«email уже есть» — линк существующей учётки.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 14:10:41 +03:00
lokses bd176827c6 admin: search_fields у Directory — для autocomplete на карточке сотрудника
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:54:20 +03:00
lokses 6b28779e00 admin: нормализовать карточку сотрудника — меньше вкладок, inline tabs
Убрана пустая «графік»-вкладка; Mattermost/AD сгруппированы с подсказкой
про ручную привязку; группы через filter_horizontal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:53:03 +03:00
lokses c543ef6567 chat: разрешить отправку поста только с файлами, без текста
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:27:18 +03:00
lokses 8dcabd76b8 fix: убрать </script> из JS-комментария в index.html
HTML-парсер закрывал <script> на литерале в комментарии — SyntaxError на /chat/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:21:10 +03:00
lokses 7b8780c6ad chat proxy: multipart через files-only — data+files list ломает httpx
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:19:01 +03:00
lokses 95b0fd34d8 chat proxy: multipart upload без request.body после разбора Django
CSRF/парсер съедает stream — для /files пересобираем POST+FILES в httpx.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:15:02 +03:00
lokses bc04162a31 chat: брать CSRF из hx-headers — HttpOnly cookie ломала POST /chat/api
Иначе Django отдавал 403 на /users/ids, posts, channels/direct и т.п.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 13:00:31 +03:00
lokses 4c2984a81e chat: same-origin /chat/api proxy вместо прямого CORS к Mattermost
Браузер больше не ходит на chat.* с portal.* — REST идёт через Django с PAT;
WS backoff без бесконечных 403; utilities.js ждёт DOMContentLoaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 12:53:04 +03:00
lokses fe463c11af chat: вызывать chatInit после HTMX-перехода
Без этого список каналов зависал на «Загрузка…» при открытии чата из меню.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 12:45:34 +03:00
lokses a2fe4a49e3 mail + portal: самоподключение почты, веб-клиент и глобальный поиск
Сотрудник сам подключает ящик (autoconfig, вложения, пагинация списка, надёжный IMAP-поиск); в хедере работает /api/search по разделам и сущностям с ACL. Также устойчивость Mattermost-токена для чата.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 12:22:53 +03:00
lokses 3387418332 dashboard: группировка виджетов по ширине (½ / полная)
Каждому виджету можно задать ширину: «Полная» (на всю строку) или «½».
Виджеты выстраиваются в 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>
2026-09-22 10:47:14 +03:00
lokses 0057f367a2 core: кастомные страницы ошибок 404/403/500/503 + режим техработ
Единый стиль портала (логотип, тема, кнопки) через 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>
2026-09-21 23:54:34 +03:00
lokses 626aeebbb7 usernames: корректная транслитерация украинских букв в логинах
pytils (русская либа) выбрасывала і/ї/є/ґ/апостроф целиком: Кіт→kt,
Філіпʼєв→flpv, Ірина→rina. Добавлен премаппинг украинских букв в латиницу
до slugify (translit_login), общий для найма, импорта и конвертации.

Теперь: Кіт Ірина → i.kit, Філіпʼєв Дмитро → d.filipiev, Ґудзик → gudzik.

- apps/hiring/services: translit_login + _username_base через него
- import_employees_json: переиспользует _username_base
- tests: украинская транслитерация в convert_usernames

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 23:39:33 +03:00
lokses 33fdf8fa20 employees: команда конвертации логинов в формат i.ivanov
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>
2026-09-21 23:32:54 +03:00
lokses 981a481e3f employees: массовая выдача ссылок первичной установки пароля + MM-синк
Админ-страница «Ссылки на установку пароля» (Сотрудники → действия):
таблица всех активных сотрудников со статусом (пароль установлен / 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>
2026-09-21 23:28:31 +03:00
lokses 7d319a8331 tests: починка после переименования пути админки
Тесты больше не хардкодят /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>
2026-09-21 23:13:23 +03:00
lokses fa323d657c locations: недостающие модули области доступа + гейт reports
Делает 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>
2026-09-21 22:59:47 +03:00
lokses 536679925a Правки логики 2026-09-21 20:37:18 +03:00
lokses f653a06820 Change Django admin URL path 2026-09-21 20:10:02 +03:00
lokses 24331df462 dashboard: проверка доступности виджетов и плиток быстрого доступа
Виджеты теперь могут требовать доступ к разделу портала (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>
2026-09-21 19:02:48 +03:00
lokses df137e1074 dashboard: виджеты «Ближайшие события», «Мои HR-заявки», «Мои IT-заявки»
Три новых виджета к кастомизируемому дашборду:
- 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>
2026-09-21 18:53:06 +03:00
lokses d07b9c6e97 dashboard: кастомизируемый дашборд из виджетов + шаблоны по умолчанию
Дашборд теперь собирается из виджетов (приветствие, метрики, мои задачи,
новости, быстрый доступ). Сотрудник настраивает состав и порядок под себя
(вкл/выкл + перемещение ↑↓), а для тех, кто не настраивал, действует набор
по умолчанию по типу локации (Офис/Аптека/Склад), задаётся в админке.

- 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>
2026-09-21 18:42:21 +03:00
lokses b2f0c2a1e2 core/portal: сотрудник без группы видит только базовые разделы
Ранее разделы с 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>
2026-09-21 18:26:15 +03:00
lokses a62f023a90 employees: сводка итоговых разрешений на карточке сотрудника
На карточке 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>
2026-09-21 17:21:58 +03:00
lokses 3ec5fb58c4 hiring: автоназначение ролей по типу локации
При утверждении найма сотрудник получает роли (группы) по умолчанию для
своего типа локации (Аптека/Офис/Склад). Наборы настраиваются в
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>
2026-09-21 16:16:22 +03:00
lokses cfafe2f0b3 hiring: логин в формате <буква имени>.<фамилия>
Иван Иванов -> i.ivanov (транслит pytils, нижний регистр, коллизия +счётчик).
Раньше slugify('Фамилия.Имя') терял точку и давал ivanovivan. Единый формат
в найме (_generate_username) и в импорте (transliterate_username).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 15:52:57 +03:00
lokses 7074244479 admin: полная матрица доступа групп в админке
Кнопка «Матрица доступа» на списке групп (unfold actions_list) открывает
сетку: строки — разделы портала, колонки — все группы, галочки просмотра.
Сохранение через apply_full_access_matrix (CUD не трогает — они на карточке
группы). Логика переиспользована из user_groups_portal. Смоук-тесты рендера
и сохранения.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 15:33:25 +03:00
lokses 385fa07eef admin: единое управление ролями в «Группы доступа»
Две сущности (auth.Group + PermissionGroup-профиль) слиты в одну страницу
админки Group: имя, разделы портала (curated), прочие права, описание,
активна, ссылка на матрицу доступа. Отдельный пункт «Группы разрешений»
убран из меню; модель PermissionGroup остаётся профилем под капотом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 15:17:57 +03:00
lokses 2a1571ef73 chat/htmx: CSRF-заголовок для всех htmx-запросов
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>
2026-09-16 20:35:07 +03:00
lokses 58efe33070 mattermost: автозаполнение бота из Mattermost по токену
Если у бота задан только 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>
2026-09-16 20:32:34 +03:00
lokses 982c7e6203 mattermost: возможности бота галочками вместо одной роли
Бот больше не привязан к единственной 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>
2026-09-16 20:27:09 +03:00
lokses e885e2924b core: команда авто-очистки (cleanup)
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>
2026-09-16 16:44:25 +03:00
lokses c54a3b316a mattermost: журнал синхронизации (как у AD/Carbonio/Nextcloud)
- модель MattermostSyncLog (employee/action/status/message) + read-only админка;
- запись при добавлении в команды/каналы (успех и ошибка) в sync_service;
- пункт «Журнал Mattermost» в меню рядом с прочими журналами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-16 16:40:08 +03:00
lokses dd1a31b4dd admin: полное и упорядоченное меню сайдбара (UNFOLD)
Навигация покрывает все админки (55 пунктов в 11 группах): Сотрудники,
Найм, Структура компании, Учёт посещений, IT-отдел, Эксплуатация (РРО/
счётчики/энергоактивы), Отчёты, Лента и задачи, База знаний, Mattermost
(вкл. Боты), Интеграции и настройки. Все ссылки проверены на reverse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-16 16:32:44 +03:00
lokses 704d5d9538 mattermost: синхронизация команд/каналов по типу локации
Добавлен фильтр по user_location_type (Аптека/Склад/Офис) в TeamSyncSettings
и ChannelSyncSettings — наряду с фильтром по отделам. При включении обоих
фильтров условия объединяются по AND.

- поля filter_by_location_type + location_types (ArrayField, чекбоксы в админке);
- логика add_user_to_teams/channels учитывает тип локации;
- django.contrib.postgres в INSTALLED_APPS (для ArrayField).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-16 16:17:16 +03:00
lokses 32ad359879 Publish IT monthly as editable Word; fix staff order and zones.
Form Word draft for OnlyOffice, publish the file to unlock the month; roster is head-first then by name with Employee responsibility zones.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:54:38 +03:00
lokses 598d26c52c Show employee login only to self and portal admins.
Managers and HR keep contact PII but no longer see usernames.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:25:02 +03:00
lokses bf5310e489 Hide peer logins on profiles and restrict view_all to published reports.
Login stays visible only to self/manager/HR; draft report docs stay department-head only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:20:49 +03:00
lokses 555dbfb157 Add Word/Excel upload and draft/publish for department report docs.
Managers can upload files as drafts; members only see published documents.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:00:27 +03:00
lokses 31eecb5001 Count IT infra work as remote in monthly report stats.
Infra/leadership channel no longer looks like on-site visits; labels clarify remote-only infrastructure.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 14:17:08 +03:00
lokses f0a23caa7b fix permisions 2026-09-14 14:09:37 +03:00