# Полное удаление сотрудника Администратор может полностью удалить аккаунт сотрудника через существующий маршрут управления пользователями: ```http DELETE /api/v1/users/admin/users/{user_id}/ Authorization: Bearer ``` Тело запроса не требуется. Успех — `204 No Content` без JSON-тела. Сотрудник исчезает из списка, поиска и карточки; повторный DELETE возвращает 404. Деактивация и обратная активация остаются отдельными операциями. ## Права и ошибки Право удаления имеет существующий активный пользователь с `is_staff=True`, как и для остальных административных endpoint'ов. Проверка повторяется внутри транзакции после получения блокировок: ранее аутентифицированный, но уже удалённый или лишённый прав администратор не может завершить изменение. | HTTP | Код ошибки | Значение | |---|---|---| | 400 | `self_delete_forbidden` | Нельзя удалить самого себя | | 401 | Стандартная ошибка аутентификации | Нет действующего access token | | 403 | Стандартная ошибка прав / `permission_denied` | Требуется активный администратор | | 404 | `not_found` | Сотрудник отсутствует, в том числе после удаления | | 409 | `last_active_admin` | Нельзя оставить систему без активного администратора | | 409 | `user_delete_conflict` | Защищённая связь препятствует удалению | Ошибки используют стандартный envelope проекта: `success: false`, `data: null`, `errors: [{code, message}]`, `meta`. Запрет удаления себя имеет приоритет над проверкой последнего администратора. Существующие связи не блокируют удаление; 409 для защищённой связи сохраняет безопасное поведение при расширении модели. DELETE, создание аккаунта администратором, смена роли/активности и соответствующие операции Django admin используют одни блокировки пользователей. Пакетное действие Django admin атомарно: если выбран сам исполнитель, вся операция отклоняется. Сохранение собственных учётных данных и смена пароля также блокируют актуальную строку User, чтобы начавшийся до DELETE запрос не создал аккаунт заново. ## Данные после удаления - Физически удаляются User, Profile, связи с группами/permissions, записи OutstandingToken и связанные BlacklistedToken. Сами справочники прав остаются. - Отчёты ReportUpload, загрузки реестров RegisterUpload, ExchangePackageImport сохраняются; их ссылки на автора обнуляются штатным SET_NULL. Сохраняются загруженные файлы, предметные данные и цепочки импортов. - BackgroundJob сохраняются, `user_id` очищается явно, поскольку это integer, а не внешний ключ. Выполняющиеся задачи не отменяются. - Аватар удаляется из storage после commit, если его не использует другой профиль. При ошибке storage удаление аккаунта остаётся успешным, ошибка регистрируется в серверном журнале для последующей очистки файла. - Технический Django admin LogEntry сохраняет штатный CASCADE для записей, исполнителем которых был удалённый пользователь; предметные журналы выше не удаляются. Содержимое исторических документов не переписывается. - Старый access перестаёт проходить JWTAuthentication. Refresh endpoint дополнительно проверяет существование и активность пользователя и возвращает 401 для удалённого/неактивного аккаунта. Повторный refresh активного пользователя поддерживается как раньше. Записей восстановления пароля в текущей модели нет. Изменений схемы БД и миграций не требуется. Откат версии приложения не восстанавливает уже удалённые аккаунты. ## Передача frontend-разработчику (Глебу) 1. Добавить отдельное действие «Удалить» в меню сотрудника; для текущего пользователя скрыть или отключить его. 2. Перед запросом показать имя выбранного сотрудника и подтверждение полного необратимого удаления аккаунта. Сообщить, что отчёты и история импортов останутся. 3. Вызвать DELETE; на время запроса блокировать повторное действие. После 204 закрыть диалог и обновить список/счётчик; если последняя строка страницы удалена, перейти на предыдущую существующую страницу. 4. Обработать стандартный error envelope, включая 400/403/404/409. При 404 обновить список. Не пытаться читать JSON из успешного ответа 204. 5. Обновить клиент из OpenAPI и проверить удаление обычного, неактивного и другого административного аккаунта, запрет удаления себя и потерю прав во время запроса. ## Проверка backend ```bash PYTHONPATH=src uv run --no-sync pytest tests/apps/user ``` Восемь тестов `test_management_concurrency.py` пропускаются SQLite и требуют PostgreSQL. Для них использовать отдельную пустую тестовую БД и явно задавать все `TEST_POSTGRES_HOST`, `TEST_POSTGRES_PORT`, `TEST_POSTGRES_USER`, `TEST_POSTGRES_PASSWORD`, `TEST_POSTGRES_DB`, чтобы исключить fallback к рабочей БД: ```bash scripts/run-tests-prod.sh ../tests/apps/user ``` Регрессии обмена и подготовленных выгрузок находятся в `tests/apps/exchange/test_api.py`, `tests/apps/external_data/test_source_record_export.py` и `tests/apps/external_data/test_export_tasks.py`. Изменений upload/export API эта доработка не вносит.