Commit Graph

128 Commits

Author SHA1 Message Date
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
lokses f46e0d90ba FIX PAGES AND ADD PASSWORD CHANGE 2026-09-11 14:51:55 +03:00
lokses f061c8f95b Email client fix 2026-09-11 14:27:39 +03:00
lokses 166a0bea71 Email client fix 2026-09-11 14:23:07 +03:00
lokses ea96c7a2ab mail: корректный парсинг UID из ответа FETCH
Извлекаем UID регуляркой `UID (\d+)` вместо хрупкого split — прежний код
давал нечисловой/пустой uid (`456 FLAGS (\Seen`), из-за чего открытие письма
падало (UID FETCH с битым аргументом). Записи без UID пропускаем.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 14:10:25 +03:00
lokses 6935ee2d59 mail: не падать 500 при битом uid / IMAP BAD
- fetch_message валидирует uid (только цифры) → иначе «Лист не знайдено»
  вместо malformed UID FETCH (сервер отвечал BAD zero-length content).
- _with_imap ловит imaplib.IMAP4.error/OSError → страница /email/ отдаёт
  connection_error, а не 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 14:06:17 +03:00
lokses b19dc8b129 Измение дизайна документов 2026-09-11 13:51:39 +03:00
lokses b730255654 Измение дизайна документов 2026-09-11 13:36:07 +03:00
lokses 2092f096a0 Измение дизайна документов 2026-09-11 13:29:18 +03:00
lokses 999cc0f5c5 nextcloud/onlyoffice: доработка редактора и браузера документов
- правки onlyoffice.py/views.py/webdav_service.py под callback/контент и ключи;
- blank_office перенесён в nextcloud_connector (генерация пустых Word/Excel);
- documents.js + обновление шаблонов браузера/редактора/карточки документов;
- тесты коннектора расширены.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 13:06:34 +03:00
lokses 6f299890d2 fix: таймзона Europe/Kiev → Europe/Kyiv
В свежей tzdata (Debian 13 / Python 3.13) старое имя Europe/Kiev удалено,
Django 6 отвергает его (ValueError: Incorrect timezone setting). Переименовано
во всех местах, включая дефолты Mattermost-таймзоны.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 17:11:14 +03:00
lokses 2781a6e2b8 reports: документы отдела с правкой через OnlyOffice
- ReportDocument (+ миграция 0005): файлы отдела в Nextcloud (06_Отчёты).
- blank_office.py: генерация пустых Word/Excel для новых документов.
- documents.py: доступ/путь/создание, права view/edit по отделу.
- редактор OnlyOffice учится открывать report_doc (view/edit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 16:48:08 +03:00
lokses 83ebf8f12a deps: чистый requirements.txt + недостающие рантайм-зависимости
- pyotp, qrcode — были не указаны, но нужны для 2FA (twofa.py); на чистом
  проде без них 2FA бы не поднялась.
- defusedxml, fonttools, pyasn1, typing_extensions — транзитивные (fpdf2/ldap3).
- gunicorn сохранён.

Список сверен с венвом проекта; системный мусор Fedora (pip freeze не того
интерпретатора) сюда не попадает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 16:48:08 +03:00
lokses 03fe40cc26 Merge pull request 'Add configurable pharmacy card tab visibility matrix.' (#2) from cursor/pharmacy-card-visibility into main
Reviewed-on: #2
2026-09-10 16:19:26 +03:00
lokses f7f78f20fd reports: список отчётов со статусом публикации
report_list_rows() строит строки хаба /reports/ (отчёт, отдел, период,
статус: опубликован/черновик/нет публикаций) — обновлён шаблон и тесты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:26:58 +03:00
lokses 3812098970 docs: мануал развёртывания в прод
Полная первичная установка на боевой сервер (LXC за Traefik, gunicorn,
nginx, PostgreSQL, Redis c requirepass, Celery worker+beat), настройка
интеграций (Mattermost/Nextcloud/OnlyOffice/почта/AD/PNMS-PVMS-MSorter),
бэкапы, обновление и типичные грабли. Дополняет перенос-на-прод.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:25:45 +03:00
lokses 3ce3536e37 core: миграция extra_config для конфига OnlyOffice-коннектора
Догенерённая миграция под правку поля ConnectorConfig.extra_config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:19:48 +03:00
lokses 8db8f59d2f core: индивидуальные гранты отчётов в группах пользователей
Выдача/отзыв права на отчёты по всем отделам конкретному сотруднику
из портала управления группами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:17:41 +03:00
lokses 4d4537deff reports: публикация отчётов и право управления
- Публикация отчёта (модель ReportPublication + миграция, сервис publication).
- Разделение прав: user_can_manage_it_monthly_report (правка) vs view;
  reports.view_all_departments в списке портальных прав.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:17:41 +03:00