diff --git a/v2/swagger.yaml b/v2/swagger.yaml index c29476c..1d6a992 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -69,6 +69,10 @@ tags: description: Methods for admin wallet transactions — manual and excel-based credit/debit. - name: file-processing description: Methods for polling async file processing (excel import/export, archives). Scoped to current user's files. + - name: user-list-cache + description: Methods for caching ad-hoc employee selections to pass to mass operations (e.g. wallet credit/debit). + - name: file + description: Methods for uploading files via signed-URL (S3-style two-step flow). Required before any flow that consumes file_id (e.g. wallet excel import). - name: user-api-token description: Methods for managing personal API tokens (PATs) for server-to-server access. Requires "api-tokens-manage" permission. - name: admin-user-api-token @@ -556,6 +560,229 @@ paths: 500: description: Server Error + /user/index: + get: + tags: + - user + summary: Получить список сотрудников + description: | + Возвращает постраничный список активных сотрудников тенанта. + Выдача ограничена пользователями со статусом `ACTIVE`; скрытые + и не валидные для отображения записи исключаются. + + Эндпоинт может использоваться интеграциями для получения + идентификаторов пользователей (`user_id`), которые в дальнейшем + передаются в `/user-list-cache/set-user-list` или другие методы + массовых операций. + + Сортировка поддерживается по нескольким полям, включая `name`, + `created_at`, `last_login_at`. Размер страницы по умолчанию — 10, + максимально допустимый — 20. + operationId: userIndex + parameters: + - name: page + in: query + schema: { type: integer, minimum: 1, default: 1 } + - name: per-page + in: query + schema: { type: integer, minimum: 1, maximum: 20, default: 10 } + - name: sort + in: query + schema: { type: string } + description: | + Поле сортировки. Для сортировки по убыванию используется префикс `-`. + Примеры: `-created_at`, `name`, `-last_login_at`. + - name: searchQuery + in: query + schema: { type: string } + description: | + Сквозной поиск по имени, фамилии, e-mail, телефону и должности. + - name: id + in: query + schema: + oneOf: + - type: string + format: uuid + - type: array + items: { type: string, format: uuid } + description: | + Идентификатор пользователя. Допускается передача нескольких + значений в виде массива `?id[]=...&id[]=...`. + - name: not_id + in: query + schema: { type: string, format: uuid } + description: Исключить пользователя с указанным идентификатором. + - name: email + in: query + schema: { type: string } + description: Поиск по электронной почте. + - name: phone_auth + in: query + schema: { type: string } + description: Поиск по номеру телефона. + - name: name + in: query + schema: { type: string } + description: Поиск по полному имени. + - name: first_name + in: query + schema: { type: string } + description: Поиск по имени. + - name: last_name + in: query + schema: { type: string } + description: Поиск по фамилии. + - name: middle_name + in: query + schema: { type: string } + description: Поиск по отчеству. + - name: position + in: query + schema: { type: string } + description: Поиск по должности. + - name: company + in: query + schema: { type: string } + description: Поиск по наименованию компании. + - name: department_id + in: query + schema: + oneOf: + - type: string + format: uuid + - type: array + items: { type: string, format: uuid } + description: Идентификатор подразделения. + - name: reg_status_id + in: query + schema: { type: string } + description: | + Статус регистрации пользователя. Допустимые значения: + `1` — без аккаунта, `2` — приглашён, `3` — активен, + `4` — заблокирован. Допускается передача нескольких значений + через запятую. + - name: gender_id + in: query + schema: { type: string } + description: Идентификатор пола. + - name: is_boss + in: query + schema: { type: boolean } + description: Признак руководящей должности. + - name: is_external + in: query + schema: { type: boolean } + description: Признак внешнего сотрудника. + - name: group_id + in: query + schema: { type: string, format: uuid } + description: Идентификатор группы сотрудников. + - name: duty_id + in: query + schema: { type: string, format: uuid } + description: Идентификатор обязанности. + - name: spec_id + in: query + schema: { type: string, format: uuid } + description: Идентификатор специализации. + - name: created_at + in: query + schema: { type: string } + description: | + Период создания записи в формате `<начало>|<конец>`, + где даты указаны по ISO 8601. + - name: last_login_at + in: query + schema: { type: string } + description: | + Период последнего входа в систему в формате `<начало>|<конец>`. + - name: user_list_key + in: query + schema: { type: string, format: uuid } + description: | + Ключ закэшированной выборки пользователей, полученный + от `/user-list-cache/set-user-list`. + - name: expand + in: query + schema: { type: string } + description: | + Список дополнительных полей через запятую. Поддерживаемые + значения: `isBoss`, `faceUrl`, `iconUrl`, `photoUrl`, + `employment`, `cityName`, `statusText`, + `profileCorporateContacts`, `isFavoriteUser`, `contacts`, + `statusTypes`. + responses: + 200: + description: Список сотрудников. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/userProfile' + _links: + $ref: '#/components/schemas/_links' + _meta: + $ref: '#/components/schemas/_meta' + 401: + description: Запрос не авторизован. + + /user/search: + get: + tags: + - user + summary: Найти сотрудников по строке поиска + description: | + Компактный поиск пользователей по строке. Возвращает до `limit` + записей с минимальным набором полей, достаточным для отображения + в выпадающих списках и подсказках. + + Поле `users` в ответе содержит пустой массив, если параметр `q` + не передан либо у текущего пользователя отсутствует право + `show-structure-names`. + operationId: userSearch + parameters: + - name: q + in: query + required: true + schema: { type: string, maxLength: 255 } + description: | + Строка поиска. Применяется к полному имени, электронной почте, + должности и наименованию подразделения. + - name: limit + in: query + schema: { type: integer, minimum: 1, default: 10 } + description: Максимальное количество возвращаемых записей. + - name: offset + in: query + schema: { type: integer, minimum: 0, default: 0 } + description: Смещение выборки. + responses: + 200: + description: Результаты поиска. + content: + application/json: + schema: + type: object + properties: + q: + type: string + users: + type: array + items: + type: object + properties: + id: { type: string, format: uuid } + name: { type: string } + employmentId: { type: string, format: uuid, nullable: true } + position: { type: string } + departmentName: { type: string } + isBoss: { type: boolean } + iconUrl: { type: string, nullable: true } + /user/items: get: tags: @@ -7112,14 +7339,37 @@ paths: /admin/wallet-transaction/create: post: tags: [admin-wallet-transaction] - summary: Провести начисление/списание по форме + summary: Провести начисление или списание по форме description: | - Массовая операция через `usersListKey` (ключ закэшированной выборки сотрудников). - Запускается асинхронно через launcher. + Массовая операция по выборке сотрудников. Выполняется асинхронно; + баланс и история транзакций обновляются в течение нескольких секунд + после успешного приёма запроса. - Soft-error состояния возвращаются в `message.type=warning`: - - "Укажите сотрудников" — выборка пустая - - "Невозможно провести транзакцию, у некоторых сотрудников недостаточно средств..." — debit, не хватает баланса (имена в тексте) + ## Получение значения `usersListKey` + + 1. Выборка сотрудников сохраняется методом + `POST /api/v2/user-list-cache/set-user-list` с телом + `{key, userList: {usersId: [...]}}`. Срок жизни сохранённой + выборки — 24 часа. + 2. Использованное значение `key` передаётся в данный метод + в качестве параметра `usersListKey`. + + Подробное описание формата и пример приведены в описании метода + `/user-list-cache/set-user-list`. + + ## Состояния, возвращаемые с предупреждением + + Ответ имеет код `200 OK` и содержит `message.type = "warning"` + в следующих случаях. + + - `"Укажите сотрудников"` — выборка по `usersListKey` пустая. + Возможные причины: ключ не найден, срок его действия истёк + либо запрос `set-user-list` был выполнен в формате + `application/x-www-form-urlencoded` без последующей корректной + передачи `userList`. + - `"Невозможно провести транзакцию, у некоторых сотрудников + недостаточно средств на балансе: <имена>"` — debit, не хватает + баланса хотя бы у одного юзера; вся операция отменяется. operationId: adminWalletTransactionCreate requestBody: required: true @@ -7271,6 +7521,180 @@ paths: schema: $ref: '#/components/schemas/FileProcessing' + /file/prepare-upload: + post: + tags: [file] + summary: Зарегистрировать файл и получить ссылку для загрузки + description: | + Первый шаг двухэтапной загрузки. Создаёт запись о файле и + возвращает временную подписанную ссылку для прямой загрузки + содержимого в объектное хранилище. + + ## Последовательность вызовов + + 1. `POST /api/v2/file/prepare-upload` с метаданными файла. + В ответе возвращаются запись `File` и объект `uploadInfo`, + содержащий ссылку `signedUrl`. + 2. `PUT ` с содержимым файла в теле запроса + и заголовком `Content-Type`, соответствующим типу файла. + Запрос выполняется напрямую в объектное хранилище. + 3. `POST /api/v2/file/finish-upload?id=` — подтверждает + завершение загрузки. После этого идентификатор файла может + быть использован в методах, ожидающих параметр `file_id` + (например, `POST /admin/wallet-transaction/excel-processing`). + + ## Ошибки валидации + + При несоответствии файла ограничениям по типу или размеру + возвращается объект вида `{error: {message, validation: {...}}}`. + + ## Идемпотентность + + При передаче в теле запроса значения существующего идентификатора + возвращается имеющаяся запись. Если файл ещё не загружен — + в ответе также присутствует объект `uploadInfo` с новой ссылкой. + Для уже загруженного файла объект `uploadInfo` в ответе отсутствует. + operationId: filePrepareUpload + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FilePrepareUploadBody' + responses: + 200: + description: Файл зарегистрирован, ссылка для загрузки получена. + content: + application/json: + schema: + $ref: '#/components/schemas/FilePrepareUploadResponse' + + /file/finish-upload: + post: + tags: [file] + summary: Подтвердить завершение загрузки файла + description: | + Второй шаг двухэтапной загрузки. Переводит запись о файле + в состояние UPLOADED после успешной передачи содержимого + по ссылке, полученной от `/file/prepare-upload`. + + До вызова этого метода идентификатор файла не может быть + использован в методах, требующих загруженный файл. Например, + `POST /admin/wallet-transaction/excel-processing` отклонит + запрос со ссылкой на файл, не имеющий статуса UPLOADED. + operationId: fileFinishUpload + parameters: + - name: id + in: query + required: true + schema: { type: string, format: uuid } + description: | + Идентификатор файла, полученный от `/file/prepare-upload`. + responses: + 200: + description: Загрузка файла подтверждена. + content: + application/json: + schema: + type: object + properties: + File: + $ref: '#/components/schemas/File' + 400: + description: Параметр `id` не передан или имеет некорректный формат. + 404: + description: Файл с указанным идентификатором не найден. + + /user-list-cache/set-user-list: + post: + tags: [user-list-cache] + summary: Сохранить выборку сотрудников и получить её идентификатор + description: | + Сохраняет в кэше выборку сотрудников, описанную через поля + `UserListForm` (списки идентификаторов пользователей, + подразделений, групп, динамические условия и т.п.), и связывает + её с переданным ключом `key`. Полученный ключ используется + в методах массовых операций — например, передаётся в качестве + значения `usersListKey` в `POST /api/v2/admin/wallet-transaction/create`. + + Время жизни ключа в кэше — 24 часа. Повторный вызов с тем же + значением `key` перезаписывает ранее сохранённую выборку. + + ## Требования к формату запроса + + Поле `userList` представляет собой вложенный объект. + Запрос должен передаваться в формате `application/json`. + При передаче запроса в формате `application/x-www-form-urlencoded` + вложенный объект не разбирается сервером, выборка сохраняется + пустой, а последующие массовые операции возвращают предупреждение + вида `{type: "warning", text: "Укажите сотрудников"}`. + + ## Пример запроса + + ```json + POST /api/v2/user-list-cache/set-user-list + Content-Type: application/json + Authorization: Bearer hrb_... + + { + "key": "integration-key-2026-05-22", + "userList": { + "usersId": ["25840ad3-a07f-4893-aa70-3a696b79a6f1"] + } + } + ``` + + Значение `key` формируется клиентом и может содержать + до 50 символов. + operationId: userListCacheSet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserListCacheSetBody' + responses: + 200: + description: | + Успешное сохранение выборки (`data: true`) либо ошибки валидации + в стандартном формате `httpJsonResponse`. + content: + application/json: + schema: + $ref: '#/components/schemas/httpJsonResponse' + + /user-list-cache/user-list-data: + get: + tags: [user-list-cache] + summary: Получить состав сохранённой выборки сотрудников + description: | + Возвращает развёрнутый состав выборки по её ключу, включая + списки пользователей, подразделений и групп, исходное описание + выборки и нормализованные условия. Используется для + предварительного просмотра перед выполнением массовых операций. + + Если ключ не найден или срок его действия истёк, поле `userList` + возвращается пустым, прочие коллекции — пустыми массивами. + operationId: userListCacheData + parameters: + - name: key + in: query + required: true + schema: { type: string, maxLength: 50 } + description: Идентификатор сохранённой выборки. + responses: + 200: + description: Состав выборки. + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/httpJsonResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/UserListData' + /user-api-token: get: tags: [user-api-token] @@ -12421,7 +12845,12 @@ components: file_id: type: string format: uuid - description: ID предварительно загруженного xlsx-файла + description: | + UUID предварительно загруженного xlsx-файла. Загружается двумя + шагами через `/file/prepare-upload` → PUT по signedUrl → + `/file/finish-upload`. Файл должен быть в статусе UPLOADED + (`file_status_id = 5`), иначе сервер вернёт ошибку + «Ошибка загрузки файла». mappings: type: array description: | @@ -12521,6 +12950,245 @@ components: _meta: $ref: '#/components/schemas/_meta' + UserListForm: + type: object + description: | + Описание выборки сотрудников. Все поля являются опциональными; + указанные критерии объединяются по логическому ИЛИ — пользователь + включается в выборку при соответствии хотя бы одному из них. + properties: + usersId: + type: array + description: Список идентификаторов пользователей. + items: + type: string + format: uuid + departmentsId: + type: array + description: | + Список идентификаторов подразделений. В выборку включаются + все сотрудники указанных подразделений. + items: + type: string + format: uuid + isDepartmentRecursive: + type: boolean + default: false + description: | + При значении `true` в выборку включаются также сотрудники + вложенных подразделений. + userGroupsId: + type: array + description: Список идентификаторов статических групп сотрудников. + items: + type: string + format: uuid + userGroupConditions: + type: array + description: | + Динамические условия выборки. Каждый элемент — объект + со структурой `{condition, operator, values}`. + items: + type: object + employmentStatuses: + type: array + description: Фильтр по статусам трудоустройства. + items: + type: integer + fileProcessingId: + type: string + format: uuid + nullable: true + description: | + Идентификатор записи `FileProcessing` с результатом обработки + xlsx-файла. При указании выборка формируется из этого результата. + + UserListCacheSetBody: + type: object + required: [key, userList] + properties: + key: + type: string + maxLength: 50 + description: | + Идентификатор выборки. Формируется клиентом. То же значение + передаётся в методы массовых операций в качестве `usersListKey`. + example: "integration-key-2026-05-22" + userList: + $ref: '#/components/schemas/UserListForm' + + UserListData: + type: object + description: | + Развёрнутый состав сохранённой выборки. Возвращается + в ответе `GET /user-list-cache/user-list-data`. + properties: + userList: + $ref: '#/components/schemas/UserListForm' + users: + type: array + description: Пользователи, входящие в выборку. + items: + type: object + departments: + type: array + description: Подразделения, указанные в выборке. + items: + type: object + userGroups: + type: array + description: Группы, указанные в выборке. + items: + type: object + userGroupConditions: + type: array + description: Динамические условия выборки. + items: + type: object + userGroupConditionsReadable: + type: array + description: Динамические условия выборки в текстовом представлении. + items: + type: object + userGroupConditionValues: + type: array + nullable: true + description: Значения, разрешённые в динамических условиях выборки. + items: + type: object + fileProcessing: + allOf: + - $ref: '#/components/schemas/FileProcessing' + nullable: true + + File: + type: object + description: Запись о файле, хранящемся в HRBox. + properties: + id: { type: string, format: uuid } + name: + type: string + description: 'Имя файла. Пример: `wallet_import.xlsx`.' + ext: + type: string + description: Расширение файла без точки. + fid: + type: string + description: | + Внутренний идентификатор файла в хранилище. + Обычно имеет вид `.`. + url: + type: string + format: uri + description: | + Публичный URL для доступа к файлу. + Имеет вид `https://<домен-тенанта>/file/open/`. + file_type: + type: string + enum: [image, video, document, archive, other] + description: Тип содержимого файла. + mime_type: + type: string + description: MIME-тип файла. + size: + type: integer + description: Размер файла в байтах. + file_status_id: + type: integer + description: | + Статус загрузки файла. Допустимые значения: + * `1` — PREPARED, запись создана, содержимое не загружено; + * `5` — UPLOADED, файл загружен и доступен для использования; + * иные значения соответствуют внутренним состояниям обработки. + is_public: + type: boolean + description: Доступность файла без авторизации. + is_common: + type: boolean + description: Признак общего файла. + is_system: + type: boolean + description: Признак системного файла. + meta: + type: object + nullable: true + description: | + Произвольные метаданные файла (например, размеры изображения). + created_at: { type: string } + user_id: + type: string + format: uuid + description: Идентификатор пользователя, загрузившего файл. + chat_channel_id: + type: string + format: uuid + nullable: true + description: | + Идентификатор канала чата, к которому привязан файл, + если применимо. + + FilePrepareUploadBody: + type: object + required: [name, ext, size, file_type] + properties: + name: + type: string + description: Имя файла. + example: "wallet_import.xlsx" + ext: + type: string + description: Расширение файла без точки. + example: "xlsx" + size: + type: integer + description: Размер файла в байтах. + example: 5120 + file_type: + type: string + enum: [image, video, document, archive, other] + description: Тип содержимого файла. + example: document + mime_type: + type: string + description: MIME-тип файла. + example: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + is_public: + type: boolean + default: false + description: | + При значении `true` файл доступен без авторизации. + Используется, например, для изображений профиля. + meta: + type: object + description: | + Произвольные метаданные файла. Например, для изображений + могут передаваться размеры: `{"width": 100, "height": 100}`. + id: + type: string + format: uuid + description: | + Идентификатор существующей записи о файле. При передаче + возвращается имеющаяся запись (идемпотентное поведение). + + FilePrepareUploadResponse: + type: object + properties: + File: + $ref: '#/components/schemas/File' + uploadInfo: + type: object + description: | + Сведения для загрузки содержимого файла. Поле отсутствует + в ответе, если файл уже находится в статусе UPLOADED. + properties: + signedUrl: + type: string + format: uri + description: | + Временная подписанная ссылка для PUT-запроса с содержимым + файла. Запрос выполняется напрямую в объектное хранилище + и не требует авторизации HRBox. + UserApiTokenStatus: type: integer description: |