docs: add guest system requirements

This commit is contained in:
lizerk 2026-06-20 10:47:10 +03:00
parent 32500e3037
commit c51ca2e247

740
backend/guests/тз.txt Normal file
View File

@ -0,0 +1,740 @@
# Техническое задание: гостевая система
Дата подготовки: 2026-06-20
Проект: EUSRR
Область: backend Django/DRF, frontend Next.js, LDAP/Active Directory, документы, уведомления.
## 1. Цель
Разработать модуль гостевых визитов, который позволяет сотрудникам создавать заявки на приглашение внешних гостей, администраторам принимать и менять решения по этим заявкам, а системе автоматически создавать и деактивировать гостевые учетные записи в LDAP/Active Directory.
Гость не является сотрудником и не должен получать доступ к веб-системе EUSRR. LDAP-учетка гостя нужна для внешних интеграций и сервисов, которые используют AD/LDAP как каталог пользователей.
## 2. Контекст проекта
В проекте уже есть:
- backend на Django/DRF;
- пользовательская модель `employees.Employee`;
- LDAP-интеграция в `employees/ldap` с `LdapSyncState` и `LdapSyncQueue`;
- универсальные комментарии через `communications.comments_helpers`;
- универсальные уведомления через `notifications.signals.notify`;
- документный модуль `documents.Document` на django-filer;
- модуль заявлений `requests_app` с простыми статусами и комментариями;
- модуль закупок `procurement` с action-процессом `submit/approve/reject`;
- frontend на Next.js с доменными страницами `/requests`, `/procurement`, `/documents`.
Гостевую систему нужно делать отдельным доменным приложением `guests`, не расширяя `requests_app.Request`, потому что у гостевых визитов другая предметная область, другой жизненный цикл, отдельная LDAP-синхронизация и запрет логина в систему.
## 3. Термины
- Гость: внешний человек, для которого может быть создана LDAP-учетка в отдельном OU `Guests`.
- Гостевой визит: заявка сотрудника на создание или продление доступа гостя.
- Приглашающий: сотрудник, который создал заявку и несет ответственность за гостя.
- Администратор гостевых визитов: пользователь, который может рассматривать заявки и управлять гостевыми учетками.
- Запрос дополнительной информации: действие администратора, после которого заявитель должен дать развернутый комментарий или изменить данные заявки.
- Действие доступа: фактический период, когда гостевая LDAP-учетка должна быть активна.
## 4. Границы MVP
В MVP нужно реализовать:
- создание гостя и гостевого визита из одной формы;
- повторное использование уже существующего гостя при новом визите;
- черновик и отправку заявки на рассмотрение;
- одобрение, отказ, запрос дополнительной информации;
- возможность администратору менять решение после одобрения или отказа;
- комментарии по визиту через существующую систему comments;
- привязку паспортных/подтверждающих файлов через модуль документов;
- создание/обновление/деактивацию гостевой учетной записи в LDAP;
- автоматическую деактивацию доступа после окончания срока визита;
- уведомления участникам процесса;
- список и карточку визита на frontend.
Не входит в MVP:
- самостоятельный вход гостя в EUSRR;
- личный кабинет гостя;
- выдача пароля гостю через EUSRR;
- многоэтапное согласование, как в закупках;
- интеграция с физической СКУД, если она не работает напрямую через LDAP;
- электронная подпись документов.
## 5. Роли и права
### 5.1. Обычный сотрудник
Может:
- создать гостевой визит от своего имени;
- сохранить визит как черновик;
- отправить визит на рассмотрение;
- просматривать свои визиты;
- редактировать свои визиты в статусах `draft` и `needs_info`;
- добавлять комментарии к своим визитам;
- прикреплять документы к своим визитам;
- отменить свой визит до фактического завершения доступа.
Не может:
- принимать решение по визиту;
- менять LDAP-статус гостя вручную;
- редактировать чужие визиты;
- создавать гостя без визита;
- удалять аудит, решения, комментарии и LDAP-следы.
### 5.2. Администратор гостевых визитов
Администратор определяется одним из условий:
- `is_staff` или `is_superuser`;
- модельное право `guests.view_all_guestvisit`;
- модельное право `guests.decide_guestvisit`;
- модельное право `guests.manage_guestaccount`.
Может:
- видеть все гостевые визиты;
- принимать решение: одобрить или отказать;
- запросить дополнительную информацию;
- изменить ранее принятое решение;
- редактировать данные гостя и визита при необходимости;
- вручную запустить LDAP-синхронизацию по визиту или гостю;
- вручную деактивировать гостевую учетку;
- просматривать журнал решений и LDAP-событий.
### 5.3. Системные задачи
Могут:
- создать или обновить LDAP-учетку гостя после одобрения;
- деактивировать гостя по окончании срока визита;
- повторять LDAP-операции через очередь при ошибках;
- отправлять системные уведомления об ошибках LDAP и истечении доступа.
## 6. Жизненный цикл гостевого визита
### 6.1. Статусы визита
Нужно завести отдельный enum `GuestVisitStatus`:
- `draft`: черновик, виден только автору и администраторам;
- `pending`: на рассмотрении;
- `needs_info`: требуется дополнительная информация от заявителя;
- `approved`: одобрено, доступ должен быть создан или активирован;
- `rejected`: отказано, доступ не должен быть активен;
- `cancelled`: отменено заявителем или администратором;
- `expired`: срок визита истек, доступ деактивирован;
- `revoked`: доступ досрочно отозван администратором.
Статусы `approved` и `rejected` не являются окончательно заблокированными: администратор должен иметь возможность поменять решение после получения новой информации.
### 6.2. Переходы статусов
Разрешенные переходы:
- `draft -> pending`: заявитель отправил на рассмотрение;
- `pending -> approved`: администратор одобрил;
- `pending -> rejected`: администратор отказал;
- `pending -> needs_info`: администратор запросил информацию;
- `needs_info -> pending`: заявитель ответил и повторно отправил;
- `needs_info -> cancelled`: заявитель отменил;
- `approved -> rejected`: администратор изменил решение, гостевой доступ деактивируется;
- `rejected -> approved`: администратор изменил решение, гостевой доступ создается/активируется;
- `approved -> revoked`: администратор досрочно отозвал доступ;
- `approved -> expired`: системная задача закрыла доступ после срока;
- `draft|pending|needs_info -> cancelled`: заявитель или администратор отменил;
- `cancelled -> pending`: только администратор может вернуть в работу, если это нужно;
- `expired -> approved`: только администратор при продлении или исправлении срока.
Каждый переход, кроме технического повторного сохранения без изменения статуса, должен писать запись в историю решений/событий.
### 6.3. Запрос дополнительной информации
Администратор нажимает "Требуется больше информации" и обязан указать комментарий.
После этого:
- статус становится `needs_info`;
- заявитель получает уведомление;
- заявитель видит, что именно нужно уточнить;
- заявитель может изменить поля заявки и/или добавить комментарий;
- после ответа заявитель нажимает "Отправить повторно";
- статус возвращается в `pending`;
- администраторы получают уведомление о повторной отправке.
## 7. Данные гостя
Нужно завести модель `Guest`.
Обязательные поля:
- `last_name`: фамилия;
- `first_name`: имя;
- `patronymic`: отчество, опционально;
- `birth_date`: дата рождения, опционально, но рекомендуется;
- `phone`: телефон, опционально;
- `email`: email, опционально;
- `organization`: организация/компания гостя, опционально;
- `position`: должность гостя, опционально;
- `comment`: служебный комментарий, опционально;
- `created_by`: кто впервые создал гостя;
- `created_at`, `updated_at`.
LDAP-поля и состояние:
- `is_active`: активен ли гость в бизнес-смысле;
- `ldap_enabled`: должна ли LDAP-учетка быть включена сейчас;
- `ldap_username`: кэш `sAMAccountName`;
- `ldap_upn`: кэш `userPrincipalName`;
- `ldap_last_synced_at`;
- `ldap_last_error`.
Идентификация гостя:
- в MVP не хранить паспортные номера отдельными полями, если это не требуется внешней интеграцией;
- паспортные сканы и персональные документы хранить через `documents.Document`;
- если позднее потребуется хранить паспортные реквизиты, добавить отдельную модель с ограниченными правами и аудитом.
Дедупликация:
- при создании нового гостя backend должен пытаться найти существующего по `email`, `phone`, а при отсутствии контактов по ФИО и дате рождения;
- если найден возможный дубль, API должен вернуть предупреждение или список кандидатов;
- администратор может объединять или выбирать существующего гостя в форме визита.
## 8. Данные гостевого визита
Нужно завести модель `GuestVisit`.
Обязательные поля:
- `guest`: ссылка на `Guest`;
- `inviter`: сотрудник-приглашающий, `employees.Employee`;
- `purpose`: цель приглашения;
- `status`: статус визита;
- `access_starts_at`: дата/время начала доступа, nullable для бессрочного доступа;
- `access_expires_at`: дата/время окончания доступа, nullable для бессрочного доступа;
- `all_day`: флаг, что время не указано и период считается полными сутками;
- `unlimited`: флаг бессрочного доступа;
- `submitted_at`;
- `decided_by`;
- `decided_at`;
- `decision_comment`;
- `created_at`, `updated_at`.
Дополнительные поля:
- `visit_comment`: комментарий заявителя;
- `admin_comment`: внутренний комментарий администратора;
- `host_department`: отдел приглашающего на момент создания, nullable;
- `cancelled_at`, `cancelled_by`, `cancel_reason`;
- `revoked_at`, `revoked_by`, `revoke_reason`;
- `expired_at`.
Связи:
- `documents`: ManyToMany к `documents.Document` для паспорта и иных вложений;
- `history`: отдельная модель истории решений и событий;
- `ldap_events`: отдельная модель или записи в общей истории для LDAP-событий.
Правило связи с приглашающим:
- `inviter` должен быть `on_delete=PROTECT` или `SET_NULL` с обязательным сохранением snapshot-полей;
- нужно хранить снимок приглашающего на момент создания: ФИО, email, id отдела;
- если сотрудник уволен, деактивирован или удален, это должно отображаться в карточке визита и списке гостей;
- при увольнении приглашающего активные гостевые доступы должны попадать в список риска для администратора;
- автоматическая деактивация всех гостей уволенного приглашающего включается только если бизнес подтвердит это правило. До подтверждения требуется уведомлять администраторов и помечать визиты флагом `inviter_inactive`.
## 9. Правила дат и времени
### 9.1. Полные сутки
Если пользователь указал только даты без времени, визит действует полные календарные сутки в timezone проекта `Europe/Moscow`.
Пример:
- дата начала: 2026-06-15;
- дата окончания: 2026-06-18;
- фактическое начало доступа: `2026-06-15 00:00:00 Europe/Moscow`;
- фактическое окончание доступа: `2026-06-19 00:00:00 Europe/Moscow`.
То есть доступ закрывается в 00:00 следующего дня после указанной даты окончания.
### 9.2. Даты с временем
Если указано время, используются точные `access_starts_at` и `access_expires_at`.
Требования:
- окончание должно быть строго позже начала;
- нельзя создать визит с окончанием в прошлом, кроме администраторского исправления;
- timezone хранится в БД как aware datetime;
- frontend показывает даты в локальном формате пользователя, но API принимает ISO-8601.
### 9.3. Бессрочный доступ
Если `unlimited=true`:
- `access_expires_at` может быть null;
- статус не переводится в `expired` автоматически;
- LDAP-учетка остается активной до ручного отзыва, отказа, отмены или деактивации гостя;
- такие записи должны быть явно выделены в списке администраторов.
## 10. LDAP/Active Directory
### 10.1. Основное требование
Гостевые учетные записи должны создаваться в отдельном контейнере AD/LDAP, отличном от сотрудников.
Нужно добавить настройку:
- `LDAP_GUESTS_BASE`, например `OU=Guests,OU=company,DC=robotail,DC=local`.
Нельзя создавать для гостя `employees.Employee`. Иначе гость сможет попасть в существующую логику аутентификации, прав, сотрудников и отделов.
### 10.2. Модель синхронизации
Использовать существующий подход:
- `LdapSyncState(model="guest", object_pk=str(guest.pk))`;
- `LdapSyncQueue` для retry LDAP-операций;
- отдельные операции очереди: `guest_save`, `guest_disable`, `guest_delete` или `guest_sync_access`;
- отдельный сервис `guests.ldap.services.GuestLdapService`.
### 10.3. Создание LDAP-учетки
Создание выполняется после одобрения визита.
Алгоритм:
1. Проверить, что визит в статусе `approved`.
2. Проверить, что текущая дата попадает в период доступа или доступ должен быть подготовлен заранее.
3. Найти существующий LDAP DN гостя через `LdapSyncState`.
4. Если DN нет, создать LDAP `user` в `LDAP_GUESTS_BASE`.
5. Записать атрибут связи, например `employeeNumber` или отдельный настраиваемый атрибут со значением `guest:<id>`.
6. Установить `sAMAccountName`, `userPrincipalName`, `displayName`, `givenName`, `sn`, `mail`, `telephoneNumber`, `description`.
7. Установить состояние учетной записи:
- enabled, если доступ уже активен;
- disabled, если визит одобрен заранее, но дата начала еще не наступила.
8. Обновить `LdapSyncState` и поля кэша в `Guest`.
### 10.4. Запрет входа гостя в EUSRR
Нужно обеспечить несколько уровней защиты:
- не создавать `Employee` для гостя;
- `LDAP_GUESTS_BASE` не должен попадать в `LDAP_USER_BASE` для auth-поиска;
- если auth search base технически широкий, добавить фильтр, исключающий OU гостей;
- не выдавать гостю Django permissions/groups;
- не создавать JWT/session для гостя;
- не использовать гостевой DN в employee sync/import.
### 10.5. Деактивация LDAP-учетки
LDAP-учетка гостя должна быть disabled, если:
- визит отклонен;
- визит отменен;
- доступ отозван;
- срок визита истек;
- гость вручную деактивирован администратором;
- все активные/будущие одобренные визиты гостя отсутствуют.
Если у гостя несколько одобренных визитов, деактивировать LDAP-учетку можно только когда нет ни одного актуального визита, требующего активного доступа.
### 10.6. Планировщик
Нужны Celery-задачи:
- `guests.tasks.activate_due_guest_visits`: включает учетки, у которых наступило начало доступа;
- `guests.tasks.expire_guest_visits`: переводит истекшие визиты в `expired` и выключает учетки;
- `guests.tasks.process_guest_ldap_queue` или расширение существующей `employees.tasks.process_ldap_queue`;
- периодичность: минимум раз в минуту, аналогично существующей LDAP retry-задаче.
## 11. Документы и паспортные файлы
Паспортные и подтверждающие файлы нужно хранить через существующий модуль документов.
Требования:
- у `GuestVisit` должна быть связь ManyToMany `documents.Document`;
- при загрузке из формы визита backend создает `Document` с `uploaded_by=inviter`;
- документ должен быть приватным;
- документ должен быть доступен автору визита и администраторам гостевой системы;
- рекомендуется использовать тег `guest-passport` или тип документа `guest_passport`;
- в карточке визита показывать список документов, ссылку на просмотр/скачивание и статус наличия обязательных документов;
- удаление документа не должно удалять визит, но должно записывать событие в аудит.
Для MVP паспортный документ сделать опциональным или обязательным через настройку `GUESTS_REQUIRE_ID_DOCUMENT`. Если настройка включена, отправка визита на рассмотрение без документа запрещена.
## 12. История, аудит и комментарии
### 12.1. История событий
Нужно завести модель `GuestVisitEvent`.
Поля:
- `visit`;
- `actor`: сотрудник или null для системной задачи;
- `event_type`: тип события;
- `from_status`;
- `to_status`;
- `comment`;
- `metadata`;
- `created_at`.
Типы событий:
- `created`;
- `submitted`;
- `needs_info_requested`;
- `info_provided`;
- `approved`;
- `rejected`;
- `decision_changed`;
- `cancelled`;
- `revoked`;
- `expired`;
- `ldap_created`;
- `ldap_updated`;
- `ldap_enabled`;
- `ldap_disabled`;
- `ldap_failed`;
- `document_attached`;
- `document_removed`;
- `inviter_inactive_detected`.
### 12.2. Комментарии
Комментарии реализовать через `communications.comments_helpers`.
Правила:
- автор визита и администраторы могут читать и писать комментарии;
- комментарий администратора при `needs_info` обязателен;
- комментарий заявителя при повторной отправке из `needs_info` обязателен;
- комментарии не заменяют историю решений, но могут быть связаны через `system_metadata`.
## 13. Уведомления
Использовать `notifications.signals.notify`.
Нужны verbs:
- `guest_visit_submitted`;
- `guest_visit_needs_info`;
- `guest_visit_info_provided`;
- `guest_visit_approved`;
- `guest_visit_rejected`;
- `guest_visit_decision_changed`;
- `guest_visit_cancelled`;
- `guest_visit_revoked`;
- `guest_visit_expired`;
- `guest_ldap_failed`;
- `guest_inviter_inactive`.
Получатели:
- при отправке на рассмотрение: все администраторы гостевой системы;
- при `needs_info`: заявитель;
- при ответе заявителя: администраторы;
- при одобрении/отказе/смене решения: заявитель;
- при LDAP-ошибке: администраторы;
- при истечении доступа: заявитель и администраторы, если это требуется настройкой;
- при увольнении/деактивации приглашающего: администраторы.
`action_url` должен вести на frontend-страницу `/guests?visit=<id>`.
## 14. Backend API
Добавить `api/v1/guests`.
### 14.1. Роуты
Через DRF router:
- `GET /api/v1/guests/visits/`: список визитов;
- `POST /api/v1/guests/visits/`: создать визит;
- `GET /api/v1/guests/visits/{id}/`: карточка визита;
- `PATCH /api/v1/guests/visits/{id}/`: изменить визит;
- `DELETE /api/v1/guests/visits/{id}/`: удалить только черновик или выполнить soft-cancel;
- `POST /api/v1/guests/visits/{id}/submit/`;
- `POST /api/v1/guests/visits/{id}/approve/`;
- `POST /api/v1/guests/visits/{id}/reject/`;
- `POST /api/v1/guests/visits/{id}/request-info/`;
- `POST /api/v1/guests/visits/{id}/provide-info/`;
- `POST /api/v1/guests/visits/{id}/cancel/`;
- `POST /api/v1/guests/visits/{id}/revoke/`;
- `POST /api/v1/guests/visits/{id}/sync-ldap/`;
- `GET /api/v1/guests/visits/{id}/comments/`;
- `POST /api/v1/guests/visits/{id}/comments/`;
- `DELETE /api/v1/guests/visits/{id}/comments/{comment_id}/`;
- `POST /api/v1/guests/visits/{id}/documents/`;
- `DELETE /api/v1/guests/visits/{id}/documents/{document_id}/`.
Дополнительно:
- `GET /api/v1/guests/guests/`: список гостей;
- `GET /api/v1/guests/guests/{id}/`: карточка гостя;
- `PATCH /api/v1/guests/guests/{id}/`: редактирование гостя администратором;
- `POST /api/v1/guests/guests/{id}/deactivate/`;
- `POST /api/v1/guests/guests/{id}/sync-ldap/`;
- `GET /api/v1/guests/guests/search/?q=...`: поиск/дедупликация гостей.
### 14.2. Фильтры списка визитов
Поддержать query params:
- `scope=mine|all|pending_decision|active|expired|risk`;
- `status`;
- `guest_id`;
- `inviter_id`;
- `created_from`, `created_to`;
- `access_from`, `access_to`;
- `unlimited=true|false`;
- `ldap_enabled=true|false`;
- `q`: поиск по ФИО гостя, организации, цели, email/phone;
- `ordering=-created_at|created_at|access_starts_at|-access_starts_at|access_expires_at|-access_expires_at`.
Обычный пользователь при `scope=all` все равно видит только свои визиты.
### 14.3. Сериализаторы
Read serializer визита должен возвращать:
- данные визита;
- краткие данные гостя;
- краткие данные приглашающего;
- статус и display label;
- вычисляемые поля `can_edit`, `can_submit`, `can_decide`, `can_request_info`, `can_cancel`, `can_revoke`, `can_sync_ldap`;
- `is_active_now`;
- `is_expired`;
- `ldap_state`;
- `documents`;
- `comments_count`;
- последний event.
Write serializer должен принимать:
- вложенные данные гостя или `guest_id`;
- `purpose`;
- дату/время или даты без времени;
- `all_day`;
- `unlimited`;
- `visit_comment`;
- `document_ids`;
- файлы для создания документов, если реализуется multipart.
### 14.4. Валидация API
Обязательные проверки:
- авторизованность пользователя;
- наличие гостя или данных для создания гостя;
- цель приглашения не пустая;
- если `unlimited=false`, должен быть задан период доступа;
- дата окончания позже даты начала;
- при `all_day=true` входные даты нормализуются в границы суток;
- нельзя отправить черновик без обязательных документов, если включена настройка;
- обычный пользователь не может подменить `inviter`, `status`, `decided_by`;
- обычный пользователь не может менять одобренный визит напрямую;
- администратор при `approve/reject/request-info/revoke` должен передать комментарий, если это требуется настройкой.
## 15. Frontend
Добавить страницу `/guests`.
### 15.1. Список визитов
Список должен поддерживать:
- вкладки или сегментированный фильтр: "Мои", "На рассмотрении", "Активные", "Истекшие", "Риски", "Все" для админов;
- поиск по гостю, организации и цели;
- фильтры по статусу, периоду доступа, бессрочности, LDAP-статусу;
- сортировку;
- пагинацию или бесконечную загрузку;
- быстрые действия для админа: одобрить, отказать, запросить информацию;
- быстрые действия для автора: редактировать черновик/needs_info, отменить.
### 15.2. Форма создания/редактирования
Поля:
- гость: выбор существующего или создание нового;
- фамилия, имя, отчество;
- дата рождения;
- телефон;
- email;
- организация;
- должность;
- цель приглашения;
- режим периода: "полные сутки", "точное время", "бессрочно";
- дата начала;
- дата окончания;
- время начала/окончания при точном режиме;
- документы паспорта/подтверждения;
- комментарий заявителя.
Поведение:
- при вводе ФИО/email/phone искать возможные дубли;
- показывать предупреждение, если найден похожий гость;
- кнопки: "Сохранить черновик", "Отправить на рассмотрение", "Отмена";
- при `needs_info` показывать комментарий администратора и поле ответа.
### 15.3. Карточка визита
Показывать:
- статус;
- гостя и его организацию;
- приглашающего и его текущий кадровый статус;
- период доступа;
- LDAP-состояние;
- документы;
- комментарии;
- историю событий;
- действия, доступные текущему пользователю.
Для админа:
- "Одобрить";
- "Отказать";
- "Требуется больше информации";
- "Изменить решение";
- "Отозвать доступ";
- "Синхронизировать LDAP";
- "Деактивировать гостя".
## 16. Admin UI
В Django admin добавить:
- `GuestAdmin`;
- `GuestVisitAdmin`;
- `GuestVisitEventAdmin`.
Требования:
- фильтры по статусу, LDAP-статусу, бессрочности, дате доступа;
- поиск по ФИО гостя, email, phone, organization, inviter email;
- read-only поля аудита и LDAP state;
- inline для визитов гостя;
- inline или отдельная вкладка событий.
## 17. Поиск
Если используется общий search/watson:
- индексировать `Guest` по ФИО, email, phone, organization;
- индексировать `GuestVisit` по purpose и snapshot приглашающего;
- в глобальном поиске показывать гостевые записи только пользователям с правом просмотра гостей.
## 18. Безопасность и персональные данные
Требования:
- паспортные документы приватные;
- доступ к документам только автору визита и администраторам;
- не отдавать паспортные файлы в списке, только в карточке с проверкой прав;
- все решения и LDAP-операции логировать;
- не логировать содержимое паспортных документов;
- не логировать пароли LDAP;
- гостевые записи не должны попадать в Employee API;
- гостевые LDAP-учетки не должны использоваться для входа в EUSRR.
Рекомендуется:
- добавить настройку срока хранения истекших гостевых документов;
- добавить management command для отчета по бессрочным гостям;
- добавить флаг согласия/основания обработки персональных данных, если это требуется внутренними правилами.
## 19. Миграции и настройки
Нужно добавить:
- Django app `guests`;
- модели `Guest`, `GuestVisit`, `GuestVisitEvent`;
- настройки:
- `LDAP_GUESTS_BASE`;
- `GUESTS_REQUIRE_ID_DOCUMENT`;
- `GUESTS_NOTIFY_ON_EXPIRATION`;
- `GUESTS_AUTO_DISABLE_ON_INVITER_INACTIVE`;
- `GUESTS_LDAP_LINK_ATTR`;
- регистрацию app в `INSTALLED_APPS`;
- регистрацию URL в `api/v1/urls.py`;
- периодические Celery-задачи в `CELERY_BEAT_SCHEDULE`;
- permissions в `Meta.permissions`.
## 20. Тесты
### 20.1. Backend tests
Покрыть:
- создание черновика;
- отправку на рассмотрение;
- валидацию дат для полного дня;
- валидацию точного времени;
- бессрочный доступ;
- запрет подмены автора обычным пользователем;
- права просмотра своих и чужих визитов;
- approve/reject/request-info/provide-info;
- смену решения `rejected -> approved` и `approved -> rejected`;
- создание event history;
- комментарии;
- привязку документов;
- dedup поиска гостя;
- истечение визита через Celery task;
- деактивацию LDAP при истечении;
- отсутствие создания `Employee` для гостя;
- запрет попадания гостя в employee import/auth;
- очередь LDAP retry при ошибке;
- уведомления получателям.
### 20.2. Frontend tests
Покрыть:
- отображение списка;
- создание визита;
- переключение режима периода;
- обязательность документов при включенной настройке;
- отображение `needs_info`;
- действия администратора;
- отображение LDAP-ошибки;
- отсутствие кнопок без прав.
## 21. Acceptance criteria
Функциональность считается готовой, если:
- сотрудник может создать гостевой визит и отправить его на рассмотрение;
- администратор видит заявку и может одобрить, отказать или запросить информацию;
- заявитель получает уведомление о запросе информации и может ответить;
- администратор может изменить отказ на одобрение и одобрение на отказ;
- после одобрения создается или активируется LDAP-учетка гостя в `LDAP_GUESTS_BASE`;
- гость не может войти в EUSRR как сотрудник;
- после окончания срока доступ автоматически деактивируется;
- бессрочный доступ не истекает автоматически;
- документы паспорта прикрепляются через модуль документов и защищены правами;
- история решений и LDAP-событий видна в карточке;
- все ключевые действия отправляют уведомления;
- тесты backend проходят;
- frontend страница `/guests` работает в desktop и mobile layout без наложений текста и элементов.
## 22. Открытые вопросы
Перед реализацией нужно подтвердить:
1. Должен ли паспортный документ быть обязательным всегда или только для отдельных типов гостей?
2. Какие LDAP-атрибуты обязаны заполняться для внешних интеграций?
3. Нужно ли выдавать гостю пароль или внешние сервисы используют только сам факт наличия LDAP-учетки?
4. Нужно ли автоматически деактивировать гостей при увольнении приглашающего или только уведомлять администраторов?
5. Нужны ли разные категории гостей и разные сроки доступа по категориям?
6. Должен ли один гость иметь несколько параллельных визитов от разных сотрудников?
7. Нужна ли интеграция с физической СКУД или достаточно LDAP?
8. Какой срок хранения паспортных документов после истечения визита?
9. Нужно ли согласование руководителем отдела приглашающего или только администраторами?
10. Должен ли администратор указывать обязательную причину при отказе, отзыве и смене решения?