From 1032e5b522926ce2a3910119db2669a9d3b34b81 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 22 May 2026 15:51:23 +0300 Subject: [PATCH 1/4] =?UTF-8?q?H-3657:=20=D0=B7=D0=B0=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=20user-list-cache=20(=D0=B8=D1=81=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=20usersListKey)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Без этого интегратор не понимает откуда взять usersListKey для POST /admin/wallet-transaction/create. - Тег user-list-cache (Methods for caching ad-hoc employee selections...). - POST /user-list-cache/set-user-list: тело {key, userList:{usersId,...}}. В описании явно прописал что Content-Type обязательно application/json — через form-urlencoded вложенный JsonModel не парсится, и сервер молча сохраняет пустую выборку (грабли которые я сам и наступил во время smoke-теста). - GET /user-list-cache/user-list-data?key=: возвращает развёрнутую выборку (users, departments, userGroups, ...). Схема ответа проверена живьём на denis.hrbox.io — все ключи совпадают. - Схемы UserListForm, UserListCacheSetBody, UserListData. - В описании POST /admin/wallet-transaction/create добавил секцию «Как получить usersListKey» с пошаговым флоу и ссылкой. Co-Authored-By: Claude Opus 4.7 (1M context) --- v2/swagger.yaml | 215 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 210 insertions(+), 5 deletions(-) diff --git a/v2/swagger.yaml b/v2/swagger.yaml index c29476c..9676bbf 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -69,6 +69,8 @@ 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: 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 @@ -7114,12 +7116,27 @@ paths: tags: [admin-wallet-transaction] summary: Провести начисление/списание по форме description: | - Массовая операция через `usersListKey` (ключ закэшированной выборки сотрудников). - Запускается асинхронно через launcher. + Массовая операция по выборке сотрудников. Запускается асинхронно + через launcher — баланс и история транзакций обновятся в течение + нескольких секунд. - Soft-error состояния возвращаются в `message.type=warning`: - - "Укажите сотрудников" — выборка пустая - - "Невозможно провести транзакцию, у некоторых сотрудников недостаточно средств..." — debit, не хватает баланса (имена в тексте) + ### Как получить `usersListKey` + + 1. `POST /api/v2/user-list-cache/set-user-list` с телом + `{key, userList:{usersId:[...]}}` — кэширует выборку под вашим + ключом (TTL 24 часа). + 2. Этот же `key` отдаёте здесь как `usersListKey`. + + Подробности и пример — в описании метода `set-user-list`. + + ### Soft-error состояния (`200 OK` + `message.type=warning`) + + - `"Укажите сотрудников"` — выборка по `usersListKey` пустая + (ключ протух, не существует или userList отдали через form-urlencoded — + см. `set-user-list`). + - `"Невозможно провести транзакцию, у некоторых сотрудников + недостаточно средств на балансе: <имена>"` — debit, не хватает + баланса хотя бы у одного юзера; вся операция отменяется. operationId: adminWalletTransactionCreate requestBody: required: true @@ -7271,6 +7288,93 @@ paths: schema: $ref: '#/components/schemas/FileProcessing' + /user-list-cache/set-user-list: + post: + tags: [user-list-cache] + summary: Закэшировать выборку сотрудников и получить ключ + description: | + Кэширует произвольную выборку сотрудников (по `usersId`, + `departmentsId`, `userGroupsId`, динамическим условиям и т.д.) + и связывает её с переданным `key`. Дальше этот `key` можно + отдавать в массовые операции — например в + `POST /api/v2/admin/wallet-transaction/create` как `usersListKey`. + + Ключ хранится в кеше **сутки** (60 × 1440 секунд). Повторный + вызов с тем же `key` перетирает выборку. + + ### ⚠️ Content-Type обязательно `application/json` + + Поле `userList` — это вложенный JSON-объект (см. схему + `UserListForm`). Через `application/x-www-form-urlencoded` + кастомный `Model::setAttributes` не парсит вложенные JSON-модели, + и сервер сохранит **пустую** выборку. Внешне ответ будет + `{success:true}`, но при попытке провести транзакцию вы получите + `{type:"warning", text:"Укажите сотрудников"}`. Только JSON. + + ### Пример + + ```json + POST /api/v2/user-list-cache/set-user-list + Content-Type: application/json + Authorization: Bearer hrb_... + + { + "key": "my-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: | + Возвращает развёрнутый состав выборки по ключу: список пользователей + (`users`), список подразделений (`departments`), список групп + (`userGroups`), исходную форму (`userList`), нормализованные + условия групп и т.д. Полезно для preview перед массовой операцией. + + Если ключ протух (24 ч) или не существует — `userList` будет + пустым, остальные коллекции — пустыми массивами. + operationId: userListCacheData + parameters: + - name: key + in: query + required: true + schema: { type: string, maxLength: 50 } + 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] @@ -12521,6 +12625,107 @@ components: _meta: $ref: '#/components/schemas/_meta' + UserListForm: + type: object + description: | + Описание выборки сотрудников. Все поля опциональны — указывайте те, + которыми хотите задать состав. Условия объединяются через OR + (любой из критериев включает юзера). + properties: + usersId: + type: array + description: Явный список UUID сотрудников. + items: + type: string + format: uuid + departmentsId: + type: array + description: UUID подразделений. Включает всех сотрудников этих подразделений. + items: + type: string + format: uuid + isDepartmentRecursive: + type: boolean + default: false + description: Если `true` — захватывает сотрудников вложенных подразделений тоже. + userGroupsId: + type: array + description: UUID статических групп сотрудников. + items: + type: string + format: uuid + userGroupConditions: + type: array + description: | + Динамические условия выборки (как в админке UserGroup). Каждый + элемент — объект `{condition, operator, values}`. + items: + type: object + employmentStatuses: + type: array + description: Фильтр по статусам трудоустройства. + items: + type: integer + fileProcessingId: + type: string + format: uuid + nullable: true + description: UUID `FileProcessing` с распарсенным xlsx — выборка возьмётся оттуда. + + UserListCacheSetBody: + type: object + required: [key, userList] + properties: + key: + type: string + maxLength: 50 + description: | + Произвольный идентификатор выборки. Этот же ключ потом передаётся + как `usersListKey` в массовые операции. + example: "my-integration-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: Развёрнутые юзеры из выборки (с `faceUrl`). + items: + type: object + departments: + type: array + description: Развёрнутые подразделения. + items: + type: object + userGroups: + type: array + description: Развёрнутые группы. + items: + type: object + userGroupConditions: + type: array + items: + type: object + userGroupConditionsReadable: + type: array + items: + type: object + userGroupConditionValues: + type: array + nullable: true + items: + type: object + fileProcessing: + allOf: + - $ref: '#/components/schemas/FileProcessing' + nullable: true + UserApiTokenStatus: type: integer description: | -- 2.54.0 From b8d17240abb65539252fb2d7e01e32232a425454 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 22 May 2026 16:06:44 +0300 Subject: [PATCH 2/4] =?UTF-8?q?H-3657:=20=D0=B7=D0=B0=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=20/user/index=20=D0=B8=20/user/search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Источник user_id для дальнейшей передачи в user-list-cache. Раньше в swagger был только /user/profile (один по id) и /user/items (минимальный список), но не было пагинированного индекса с фильтрами — а именно через /user/index интегратор собирает получателей для массового начисления валюты. - GET /user/index: пагинация (per-page 1..20), сортировка, фильтры по основным атрибутам UserSearch (id, email, name, first_name, last_name, department_id, reg_status_id, group_id, position, ...), expand-поля. - GET /user/search?q=&limit=&offset=: быстрый компактный поиск по ФИО/email/должности. Возвращает {q, users:[{id, name, position, departmentName, isBoss, ...}]}. Требует право show-structure-names — иначе users:[]. Co-Authored-By: Claude Opus 4.7 (1M context) --- v2/swagger.yaml | 194 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/v2/swagger.yaml b/v2/swagger.yaml index 9676bbf..2655a4d 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -558,6 +558,200 @@ paths: 500: description: Server Error + /user/index: + get: + tags: + - user + summary: Список сотрудников + description: | + Постраничный список активных сотрудников тенанта. Тот же эндпоинт, + что использует основной UI HRBox. Видны все сотрудники со + `status_id = ACTIVE`, скрытые и не валидные для отображения отфильтрованы. + + ### Типичные кейсы + + - Интеграции, которым нужно собрать `user_id` для последующего + `POST /user-list-cache/set-user-list` → массового начисления валюты. + - Подразделение-специфичные выгрузки (`department_id`). + + ### Сортировка + + Поддерживается `?sort=name`, `?sort=-created_at`, `-last_login_at` и т.п. + + ### Размер страницы + + Дефолт 10, максимум 20 (`pageSizeLimit = [1, 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: 'Префикс `-` для DESC. Например `-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: 'Фильтр по конкретным UUID. Можно массив через `?id[]=...&id[]=...`.' + - name: not_id + in: query + schema: { type: string, format: uuid } + description: Исключить конкретного юзера. + - name: email + in: query + schema: { type: string } + - name: phone_auth + in: query + schema: { type: string } + - name: name + in: query + schema: { type: string } + - name: first_name + in: query + schema: { type: string } + - name: last_name + in: query + schema: { type: string } + - name: middle_name + in: query + schema: { type: string } + - name: position + in: query + schema: { type: string } + - name: company + in: query + schema: { type: string } + - name: department_id + in: query + schema: + oneOf: + - type: string + format: uuid + - type: array + items: { type: string, format: uuid } + - name: reg_status_id + in: query + schema: { type: string } + description: Comma-separated `RegStatus` (1=NO_ACCOUNT, 2=INVITED, 3=ACTIVE, 4=BLOCKED). + - name: gender_id + in: query + schema: { type: string } + - name: is_boss + in: query + schema: { type: boolean } + - name: is_external + in: query + schema: { type: boolean } + - name: group_id + in: query + schema: { type: string, format: uuid } + description: Фильтр по статической группе сотрудников. + - name: duty_id + in: query + schema: { type: string, format: uuid } + - name: spec_id + in: query + schema: { type: string, format: uuid } + - name: created_at + in: query + schema: { type: string } + description: 'Диапазон `from|to` (ISO).' + - name: last_login_at + in: query + schema: { type: string } + description: 'Диапазон `from|to` (ISO).' + - 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: | + Comma-separated extra-fields. Поддерживаются: `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: Unauthorized. + + /user/search: + get: + tags: + - user + summary: Быстрый поиск сотрудников по строке + description: | + Лёгкий компактный поиск — возвращает до `limit` юзеров с минимальным + набором полей (id, name, position, departmentName, isBoss, iconUrl, + employmentId). Использует тот же `UserSearchLogic`, что и сквозной + поиск UI. + + Поле `users` будет пустым массивом, если у текущего юзера нет права + `show-structure-names`. Также пустой массив, если `q` не передан или пустой. + operationId: userSearch + parameters: + - name: q + in: query + required: true + schema: { type: string, maxLength: 255 } + description: Строка поиска (ФИО / e-mail / должность / подразделение). + - name: limit + in: query + schema: { type: integer, minimum: 1, maximum: 100, default: 10 } + - name: offset + in: query + schema: { type: integer, minimum: 0, default: 0 } + 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: -- 2.54.0 From 37203aee1b955ad77dad7f862b0410d599d8b399 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 22 May 2026 16:49:54 +0300 Subject: [PATCH 3/4] =?UTF-8?q?H-3657:=20=D0=B7=D0=B0=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=20/file/prepare-upload=20=D0=B8=20/file/finish-upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Без этих эндпоинтов excel-import flow для wallet-транзакций неполный — интегратор не понимает откуда взять file_id для POST /admin/wallet-transaction/excel-processing. - Новый тег `file` (Methods for uploading files via signed-URL...). - POST /file/prepare-upload: создаёт File-запись + возвращает signedUrl для PUT-загрузки бинарника напрямую в S3. - POST /file/finish-upload?id=: помечает файл как UPLOADED, после чего file_id можно использовать в downstream-флоу. - Подробная описание двухшагового флоу (prepare → PUT signedUrl → finish) в описании prepare-upload. - Идемпотентность через повторный prepare-upload с тем же id. - Схемы File (полная), FilePrepareUploadBody, FilePrepareUploadResponse. - В WalletExcelFormBody.file_id ссылка на этот upload-флоу — раньше было просто «UUID предварительно загруженного xlsx». Co-Authored-By: Claude Opus 4.7 (1M context) --- v2/swagger.yaml | 189 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/v2/swagger.yaml b/v2/swagger.yaml index 2655a4d..29ac15b 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -71,6 +71,8 @@ tags: 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 @@ -7482,6 +7484,89 @@ paths: schema: $ref: '#/components/schemas/FileProcessing' + /file/prepare-upload: + post: + tags: [file] + summary: Зарегистрировать файл и получить signed URL для загрузки + description: | + Первый шаг двухступенчатой загрузки. Создаёт запись `File` в БД и + возвращает временный signed-URL для PUT-загрузки бинарника напрямую + в объектное хранилище (S3-совместимое). + + ### Полный флоу + + 1. `POST /api/v2/file/prepare-upload` с метаданными файла → получаем + `{File, uploadInfo:{signedUrl}}`. + 2. `PUT ` с телом-бинарником и `Content-Type` файла — + идёт прямо в S3, минуя HRBox. + 3. `POST /api/v2/file/finish-upload?id=` — помечает запись + как загруженную (`file_status_id` → UPLOADED). После этого + `File.id` можно использовать в любом флоу, где требуется + `file_id` (например `POST /admin/wallet-transaction/excel-processing`). + + ### Возможные ошибки + + Если файл не прошёл валидацию (тип, размер) — ответ + `{error:{message, validation:{...}}}`. На уровне антивируса — + `UnsafeFileException` 4xx. + + ### Идемпотентность + + Если передать существующий `id` в body — вернётся прежняя запись + и новый `signedUrl` (если файл ещё не uploaded). Если файл уже + uploaded — `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: | + Второй шаг загрузки. Помечает запись `File` как UPLOADED после того, + как клиент успешно загрузил бинарник по signedUrl из + `prepare-upload`. До вызова этого метода `file_id` нельзя + использовать в downstream-флоу (например, при попытке + `wallet-transaction/excel-processing` с не-UPLOADED файлом форма + отклонит запрос с ошибкой «Ошибка загрузки файла»). + + ### Параметр `id` + + Принимается из query (`?id=`) или из тела (`{id: }`). + operationId: fileFinishUpload + parameters: + - name: id + in: query + required: true + schema: { type: string, format: uuid } + description: UUID файла, полученный из `prepare-upload`. + responses: + 200: + description: Файл помечен как загруженный + content: + application/json: + schema: + type: object + properties: + File: + $ref: '#/components/schemas/File' + 400: + description: Не передан id или невалидный UUID. + 404: + description: Файл с таким id не найден. + /user-list-cache/set-user-list: post: tags: [user-list-cache] @@ -12719,7 +12804,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: | @@ -12920,6 +13010,103 @@ components: - $ref: '#/components/schemas/FileProcessing' nullable: true + File: + type: object + description: Запись файла в HRBox. Полная схема (используется в prepare/finish-upload и nested-объектах). + 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] + mime_type: { type: string } + size: + type: integer + description: Размер в байтах. + file_status_id: + type: integer + description: | + Статус загрузки: + * `1` — PREPARED (создан, бинарник не загружен) + * `5` — UPLOADED (готов к использованию) + * другие значения — внутренние состояния обработки + is_public: { type: boolean } + is_common: { type: boolean } + is_system: { type: boolean } + meta: + type: object + nullable: true + description: 'Произвольные метаданные (width/height для картинок и т.п.).' + created_at: { type: string } + user_id: + type: string + format: uuid + description: UUID юзера-загрузчика. + chat_channel_id: + type: string + format: uuid + nullable: true + + FilePrepareUploadBody: + type: object + required: [name, ext, size, file_type] + properties: + name: + type: string + example: "wallet_import.xlsx" + ext: + type: string + example: "xlsx" + size: + type: integer + description: Размер в байтах. + example: 5120 + file_type: + type: string + enum: [image, video, document, archive, other] + example: document + mime_type: + type: string + example: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + is_public: + type: integer + description: '`1` если файл должен быть доступен без авторизации (фото и т.п.), `0` — приватный.' + enum: [0, 1] + default: 0 + meta: + type: object + description: 'Произвольные метаданные (например `{"width":100,"height":100}` для картинок).' + id: + type: string + format: uuid + description: 'Опционально. Если передать существующий `id`, вернётся та же запись (идемпотентность).' + + FilePrepareUploadResponse: + type: object + properties: + File: + $ref: '#/components/schemas/File' + uploadInfo: + type: object + description: | + Информация для PUT-загрузки бинарника. Отсутствует, если файл + уже UPLOADED (повторный prepare-upload с тем же `id`). + properties: + signedUrl: + type: string + format: uri + description: | + Временный URL для PUT-запроса с телом-бинарником. + Не требует HRBox-авторизации — это прямая ссылка на S3. + UserApiTokenStatus: type: integer description: | -- 2.54.0 From 58a32b1768638eb6ea8acd96af4a02d075305e81 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 28 May 2026 17:00:40 +0300 Subject: [PATCH 4/4] =?UTF-8?q?H-3657:=20=D0=B2=D1=8B=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F=20=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B2=20MR=20!?= =?UTF-8?q?36?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Привёл описания /user/index, /user/search, /file/prepare-upload, /file/finish-upload, /user-list-cache/* и связанных схем (UserListForm, UserListData, File, FilePrepareUploadBody, FilePrepareUploadResponse) к единому нейтральному документационному стилю — как в H-3821. Убраны: разговорные обороты («юзер», «флоу», «грабли», «отдаёте», «придумываете», «протух», «дефолт», «компактный»), эмодзи в заголовках секций, ###-маркеры разделов внутри description. Все формулировки переведены в третье лицо. Поправлены данные: - is_public в FilePrepareUploadBody: integer enum [0,1] → boolean (соответствует @property boolean is_public в File model). - /user/search?limit: убран искусственный maximum: 100, в контроллере верхнего предела нет (только default = 10). Co-Authored-By: Claude Opus 4.7 (1M context) --- v2/swagger.yaml | 404 +++++++++++++++++++++++++++++------------------- 1 file changed, 243 insertions(+), 161 deletions(-) diff --git a/v2/swagger.yaml b/v2/swagger.yaml index 29ac15b..1d6a992 100644 --- a/v2/swagger.yaml +++ b/v2/swagger.yaml @@ -564,25 +564,20 @@ paths: get: tags: - user - summary: Список сотрудников + summary: Получить список сотрудников description: | - Постраничный список активных сотрудников тенанта. Тот же эндпоинт, - что использует основной UI HRBox. Видны все сотрудники со - `status_id = ACTIVE`, скрытые и не валидные для отображения отфильтрованы. + Возвращает постраничный список активных сотрудников тенанта. + Выдача ограничена пользователями со статусом `ACTIVE`; скрытые + и не валидные для отображения записи исключаются. - ### Типичные кейсы + Эндпоинт может использоваться интеграциями для получения + идентификаторов пользователей (`user_id`), которые в дальнейшем + передаются в `/user-list-cache/set-user-list` или другие методы + массовых операций. - - Интеграции, которым нужно собрать `user_id` для последующего - `POST /user-list-cache/set-user-list` → массового начисления валюты. - - Подразделение-специфичные выгрузки (`department_id`). - - ### Сортировка - - Поддерживается `?sort=name`, `?sort=-created_at`, `-last_login_at` и т.п. - - ### Размер страницы - - Дефолт 10, максимум 20 (`pageSizeLimit = [1, 20]`). + Сортировка поддерживается по нескольким полям, включая `name`, + `created_at`, `last_login_at`. Размер страницы по умолчанию — 10, + максимально допустимый — 20. operationId: userIndex parameters: - name: page @@ -594,11 +589,14 @@ paths: - name: sort in: query schema: { type: string } - description: 'Префикс `-` для DESC. Например `-created_at`, `name`, `-last_login_at`.' + description: | + Поле сортировки. Для сортировки по убыванию используется префикс `-`. + Примеры: `-created_at`, `name`, `-last_login_at`. - name: searchQuery in: query schema: { type: string } - description: Сквозной поиск по ФИО / e-mail / телефону / должности. + description: | + Сквозной поиск по имени, фамилии, e-mail, телефону и должности. - name: id in: query schema: @@ -607,35 +605,45 @@ paths: format: uuid - type: array items: { type: string, format: uuid } - description: 'Фильтр по конкретным UUID. Можно массив через `?id[]=...&id[]=...`.' + description: | + Идентификатор пользователя. Допускается передача нескольких + значений в виде массива `?id[]=...&id[]=...`. - name: not_id in: query schema: { type: string, format: uuid } - description: Исключить конкретного юзера. + 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: @@ -644,51 +652,68 @@ paths: format: uuid - type: array items: { type: string, format: uuid } + description: Идентификатор подразделения. - name: reg_status_id in: query schema: { type: string } - description: Comma-separated `RegStatus` (1=NO_ACCOUNT, 2=INVITED, 3=ACTIVE, 4=BLOCKED). + 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: Фильтр по статической группе сотрудников. + 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: 'Диапазон `from|to` (ISO).' + description: | + Период создания записи в формате `<начало>|<конец>`, + где даты указаны по ISO 8601. - name: last_login_at in: query schema: { type: string } - description: 'Диапазон `from|to` (ISO).' + description: | + Период последнего входа в систему в формате `<начало>|<конец>`. - name: user_list_key in: query schema: { type: string, format: uuid } - description: 'Ключ закэшированной выборки (см. `/user-list-cache/set-user-list`).' + description: | + Ключ закэшированной выборки пользователей, полученный + от `/user-list-cache/set-user-list`. - name: expand in: query schema: { type: string } description: | - Comma-separated extra-fields. Поддерживаются: `isBoss`, `faceUrl`, - `iconUrl`, `photoUrl`, `employment`, `cityName`, `statusText`, - `profileCorporateContacts`, `isFavoriteUser`, `contacts`, `statusTypes`. + Список дополнительных полей через запятую. Поддерживаемые + значения: `isBoss`, `faceUrl`, `iconUrl`, `photoUrl`, + `employment`, `cityName`, `statusText`, + `profileCorporateContacts`, `isFavoriteUser`, `contacts`, + `statusTypes`. responses: 200: - description: Список сотрудников + description: Список сотрудников. content: application/json: schema: @@ -703,37 +728,41 @@ paths: _meta: $ref: '#/components/schemas/_meta' 401: - description: Unauthorized. + description: Запрос не авторизован. /user/search: get: tags: - user - summary: Быстрый поиск сотрудников по строке + summary: Найти сотрудников по строке поиска description: | - Лёгкий компактный поиск — возвращает до `limit` юзеров с минимальным - набором полей (id, name, position, departmentName, isBoss, iconUrl, - employmentId). Использует тот же `UserSearchLogic`, что и сквозной - поиск UI. + Компактный поиск пользователей по строке. Возвращает до `limit` + записей с минимальным набором полей, достаточным для отображения + в выпадающих списках и подсказках. - Поле `users` будет пустым массивом, если у текущего юзера нет права - `show-structure-names`. Также пустой массив, если `q` не передан или пустой. + Поле `users` в ответе содержит пустой массив, если параметр `q` + не передан либо у текущего пользователя отсутствует право + `show-structure-names`. operationId: userSearch parameters: - name: q in: query required: true schema: { type: string, maxLength: 255 } - description: Строка поиска (ФИО / e-mail / должность / подразделение). + description: | + Строка поиска. Применяется к полному имени, электронной почте, + должности и наименованию подразделения. - name: limit in: query - schema: { type: integer, minimum: 1, maximum: 100, default: 10 } + schema: { type: integer, minimum: 1, default: 10 } + description: Максимальное количество возвращаемых записей. - name: offset in: query schema: { type: integer, minimum: 0, default: 0 } + description: Смещение выборки. responses: 200: - description: Результаты поиска + description: Результаты поиска. content: application/json: schema: @@ -7310,26 +7339,34 @@ paths: /admin/wallet-transaction/create: post: tags: [admin-wallet-transaction] - summary: Провести начисление/списание по форме + summary: Провести начисление или списание по форме description: | - Массовая операция по выборке сотрудников. Запускается асинхронно - через launcher — баланс и история транзакций обновятся в течение - нескольких секунд. + Массовая операция по выборке сотрудников. Выполняется асинхронно; + баланс и история транзакций обновляются в течение нескольких секунд + после успешного приёма запроса. - ### Как получить `usersListKey` + ## Получение значения `usersListKey` - 1. `POST /api/v2/user-list-cache/set-user-list` с телом - `{key, userList:{usersId:[...]}}` — кэширует выборку под вашим - ключом (TTL 24 часа). - 2. Этот же `key` отдаёте здесь как `usersListKey`. + 1. Выборка сотрудников сохраняется методом + `POST /api/v2/user-list-cache/set-user-list` с телом + `{key, userList: {usersId: [...]}}`. Срок жизни сохранённой + выборки — 24 часа. + 2. Использованное значение `key` передаётся в данный метод + в качестве параметра `usersListKey`. - Подробности и пример — в описании метода `set-user-list`. + Подробное описание формата и пример приведены в описании метода + `/user-list-cache/set-user-list`. - ### Soft-error состояния (`200 OK` + `message.type=warning`) + ## Состояния, возвращаемые с предупреждением - - `"Укажите сотрудников"` — выборка по `usersListKey` пустая - (ключ протух, не существует или userList отдали через form-urlencoded — - см. `set-user-list`). + Ответ имеет код `200 OK` и содержит `message.type = "warning"` + в следующих случаях. + + - `"Укажите сотрудников"` — выборка по `usersListKey` пустая. + Возможные причины: ключ не найден, срок его действия истёк + либо запрос `set-user-list` был выполнен в формате + `application/x-www-form-urlencoded` без последующей корректной + передачи `userList`. - `"Невозможно провести транзакцию, у некоторых сотрудников недостаточно средств на балансе: <имена>"` — debit, не хватает баланса хотя бы у одного юзера; вся операция отменяется. @@ -7487,34 +7524,36 @@ paths: /file/prepare-upload: post: tags: [file] - summary: Зарегистрировать файл и получить signed URL для загрузки + summary: Зарегистрировать файл и получить ссылку для загрузки description: | - Первый шаг двухступенчатой загрузки. Создаёт запись `File` в БД и - возвращает временный signed-URL для PUT-загрузки бинарника напрямую - в объектное хранилище (S3-совместимое). + Первый шаг двухэтапной загрузки. Создаёт запись о файле и + возвращает временную подписанную ссылку для прямой загрузки + содержимого в объектное хранилище. - ### Полный флоу + ## Последовательность вызовов - 1. `POST /api/v2/file/prepare-upload` с метаданными файла → получаем - `{File, uploadInfo:{signedUrl}}`. - 2. `PUT ` с телом-бинарником и `Content-Type` файла — - идёт прямо в S3, минуя HRBox. - 3. `POST /api/v2/file/finish-upload?id=` — помечает запись - как загруженную (`file_status_id` → UPLOADED). После этого - `File.id` можно использовать в любом флоу, где требуется - `file_id` (например `POST /admin/wallet-transaction/excel-processing`). + 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:{...}}}`. На уровне антивируса — - `UnsafeFileException` 4xx. + При несоответствии файла ограничениям по типу или размеру + возвращается объект вида `{error: {message, validation: {...}}}`. - ### Идемпотентность + ## Идемпотентность - Если передать существующий `id` в body — вернётся прежняя запись - и новый `signedUrl` (если файл ещё не uploaded). Если файл уже - uploaded — `uploadInfo` будет отсутствовать. + При передаче в теле запроса значения существующего идентификатора + возвращается имеющаяся запись. Если файл ещё не загружен — + в ответе также присутствует объект `uploadInfo` с новой ссылкой. + Для уже загруженного файла объект `uploadInfo` в ответе отсутствует. operationId: filePrepareUpload requestBody: required: true @@ -7524,7 +7563,7 @@ paths: $ref: '#/components/schemas/FilePrepareUploadBody' responses: 200: - description: Файл зарегистрирован, ссылка на загрузку готова + description: Файл зарегистрирован, ссылка для загрузки получена. content: application/json: schema: @@ -7535,26 +7574,25 @@ paths: tags: [file] summary: Подтвердить завершение загрузки файла description: | - Второй шаг загрузки. Помечает запись `File` как UPLOADED после того, - как клиент успешно загрузил бинарник по signedUrl из - `prepare-upload`. До вызова этого метода `file_id` нельзя - использовать в downstream-флоу (например, при попытке - `wallet-transaction/excel-processing` с не-UPLOADED файлом форма - отклонит запрос с ошибкой «Ошибка загрузки файла»). + Второй шаг двухэтапной загрузки. Переводит запись о файле + в состояние UPLOADED после успешной передачи содержимого + по ссылке, полученной от `/file/prepare-upload`. - ### Параметр `id` - - Принимается из query (`?id=`) или из тела (`{id: }`). + До вызова этого метода идентификатор файла не может быть + использован в методах, требующих загруженный файл. Например, + `POST /admin/wallet-transaction/excel-processing` отклонит + запрос со ссылкой на файл, не имеющий статуса UPLOADED. operationId: fileFinishUpload parameters: - name: id in: query required: true schema: { type: string, format: uuid } - description: UUID файла, полученный из `prepare-upload`. + description: | + Идентификатор файла, полученный от `/file/prepare-upload`. responses: 200: - description: Файл помечен как загруженный + description: Загрузка файла подтверждена. content: application/json: schema: @@ -7563,34 +7601,35 @@ paths: File: $ref: '#/components/schemas/File' 400: - description: Не передан id или невалидный UUID. + description: Параметр `id` не передан или имеет некорректный формат. 404: - description: Файл с таким id не найден. + description: Файл с указанным идентификатором не найден. /user-list-cache/set-user-list: post: tags: [user-list-cache] - summary: Закэшировать выборку сотрудников и получить ключ + summary: Сохранить выборку сотрудников и получить её идентификатор description: | - Кэширует произвольную выборку сотрудников (по `usersId`, - `departmentsId`, `userGroupsId`, динамическим условиям и т.д.) - и связывает её с переданным `key`. Дальше этот `key` можно - отдавать в массовые операции — например в - `POST /api/v2/admin/wallet-transaction/create` как `usersListKey`. + Сохраняет в кэше выборку сотрудников, описанную через поля + `UserListForm` (списки идентификаторов пользователей, + подразделений, групп, динамические условия и т.п.), и связывает + её с переданным ключом `key`. Полученный ключ используется + в методах массовых операций — например, передаётся в качестве + значения `usersListKey` в `POST /api/v2/admin/wallet-transaction/create`. - Ключ хранится в кеше **сутки** (60 × 1440 секунд). Повторный - вызов с тем же `key` перетирает выборку. + Время жизни ключа в кэше — 24 часа. Повторный вызов с тем же + значением `key` перезаписывает ранее сохранённую выборку. - ### ⚠️ Content-Type обязательно `application/json` + ## Требования к формату запроса - Поле `userList` — это вложенный JSON-объект (см. схему - `UserListForm`). Через `application/x-www-form-urlencoded` - кастомный `Model::setAttributes` не парсит вложенные JSON-модели, - и сервер сохранит **пустую** выборку. Внешне ответ будет - `{success:true}`, но при попытке провести транзакцию вы получите - `{type:"warning", text:"Укажите сотрудников"}`. Только JSON. + Поле `userList` представляет собой вложенный объект. + Запрос должен передаваться в формате `application/json`. + При передаче запроса в формате `application/x-www-form-urlencoded` + вложенный объект не разбирается сервером, выборка сохраняется + пустой, а последующие массовые операции возвращают предупреждение + вида `{type: "warning", text: "Укажите сотрудников"}`. - ### Пример + ## Пример запроса ```json POST /api/v2/user-list-cache/set-user-list @@ -7598,14 +7637,15 @@ paths: Authorization: Bearer hrb_... { - "key": "my-integration-key-2026-05-22", + "key": "integration-key-2026-05-22", "userList": { "usersId": ["25840ad3-a07f-4893-aa70-3a696b79a6f1"] } } ``` - `key` придумываете сами (до 50 символов). + Значение `key` формируется клиентом и может содержать + до 50 символов. operationId: userListCacheSet requestBody: required: true @@ -7616,8 +7656,8 @@ paths: responses: 200: description: | - Успех (`data: true`) или ошибки валидации в стандартном формате - `httpJsonResponse`. + Успешное сохранение выборки (`data: true`) либо ошибки валидации + в стандартном формате `httpJsonResponse`. content: application/json: schema: @@ -7626,24 +7666,25 @@ paths: /user-list-cache/user-list-data: get: tags: [user-list-cache] - summary: Получить состав закэшированной выборки сотрудников + summary: Получить состав сохранённой выборки сотрудников description: | - Возвращает развёрнутый состав выборки по ключу: список пользователей - (`users`), список подразделений (`departments`), список групп - (`userGroups`), исходную форму (`userList`), нормализованные - условия групп и т.д. Полезно для preview перед массовой операцией. + Возвращает развёрнутый состав выборки по её ключу, включая + списки пользователей, подразделений и групп, исходное описание + выборки и нормализованные условия. Используется для + предварительного просмотра перед выполнением массовых операций. - Если ключ протух (24 ч) или не существует — `userList` будет - пустым, остальные коллекции — пустыми массивами. + Если ключ не найден или срок его действия истёк, поле `userList` + возвращается пустым, прочие коллекции — пустыми массивами. operationId: userListCacheData parameters: - name: key in: query required: true schema: { type: string, maxLength: 50 } + description: Идентификатор сохранённой выборки. responses: 200: - description: Данные выборки + description: Состав выборки. content: application/json: schema: @@ -12912,37 +12953,41 @@ components: UserListForm: type: object description: | - Описание выборки сотрудников. Все поля опциональны — указывайте те, - которыми хотите задать состав. Условия объединяются через OR - (любой из критериев включает юзера). + Описание выборки сотрудников. Все поля являются опциональными; + указанные критерии объединяются по логическому ИЛИ — пользователь + включается в выборку при соответствии хотя бы одному из них. properties: usersId: type: array - description: Явный список UUID сотрудников. + description: Список идентификаторов пользователей. items: type: string format: uuid departmentsId: type: array - description: UUID подразделений. Включает всех сотрудников этих подразделений. + description: | + Список идентификаторов подразделений. В выборку включаются + все сотрудники указанных подразделений. items: type: string format: uuid isDepartmentRecursive: type: boolean default: false - description: Если `true` — захватывает сотрудников вложенных подразделений тоже. + description: | + При значении `true` в выборку включаются также сотрудники + вложенных подразделений. userGroupsId: type: array - description: UUID статических групп сотрудников. + description: Список идентификаторов статических групп сотрудников. items: type: string format: uuid userGroupConditions: type: array description: | - Динамические условия выборки (как в админке UserGroup). Каждый - элемент — объект `{condition, operator, values}`. + Динамические условия выборки. Каждый элемент — объект + со структурой `{condition, operator, values}`. items: type: object employmentStatuses: @@ -12954,7 +12999,9 @@ components: type: string format: uuid nullable: true - description: UUID `FileProcessing` с распарсенным xlsx — выборка возьмётся оттуда. + description: | + Идентификатор записи `FileProcessing` с результатом обработки + xlsx-файла. При указании выборка формируется из этого результата. UserListCacheSetBody: type: object @@ -12964,45 +13011,49 @@ components: type: string maxLength: 50 description: | - Произвольный идентификатор выборки. Этот же ключ потом передаётся - как `usersListKey` в массовые операции. - example: "my-integration-2026-05-22" + Идентификатор выборки. Формируется клиентом. То же значение + передаётся в методы массовых операций в качестве `usersListKey`. + example: "integration-key-2026-05-22" userList: $ref: '#/components/schemas/UserListForm' UserListData: type: object description: | - Развёрнутый состав выборки. Возвращается `GET /user-list-cache/user-list-data`. + Развёрнутый состав сохранённой выборки. Возвращается + в ответе `GET /user-list-cache/user-list-data`. properties: userList: $ref: '#/components/schemas/UserListForm' users: type: array - description: Развёрнутые юзеры из выборки (с `faceUrl`). + description: Пользователи, входящие в выборку. items: type: object departments: type: array - description: Развёрнутые подразделения. + description: Подразделения, указанные в выборке. items: type: object userGroups: type: array - description: Развёрнутые группы. + 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: @@ -13012,48 +13063,69 @@ components: File: type: object - description: Запись файла в HRBox. Полная схема (используется в prepare/finish-upload и nested-объектах). + description: Запись о файле, хранящемся в HRBox. properties: id: { type: string, format: uuid } - name: { type: string, description: 'Оригинальное имя файла, например `wallet_import.xlsx`.' } - ext: { type: string, description: 'Расширение без точки.' } + name: + type: string + description: 'Имя файла. Пример: `wallet_import.xlsx`.' + ext: + type: string + description: Расширение файла без точки. fid: type: string - description: 'Внутренний идентификатор файла в хранилище (обычно `.`).' + description: | + Внутренний идентификатор файла в хранилище. + Обычно имеет вид `.`. url: type: string format: uri - description: 'Публичный URL вида `https:///file/open/`.' + description: | + Публичный URL для доступа к файлу. + Имеет вид `https://<домен-тенанта>/file/open/`. file_type: type: string enum: [image, video, document, archive, other] - mime_type: { type: string } + description: Тип содержимого файла. + mime_type: + type: string + description: MIME-тип файла. size: type: integer - description: Размер в байтах. + description: Размер файла в байтах. file_status_id: type: integer description: | - Статус загрузки: - * `1` — PREPARED (создан, бинарник не загружен) - * `5` — UPLOADED (готов к использованию) - * другие значения — внутренние состояния обработки - is_public: { type: boolean } - is_common: { type: boolean } - is_system: { type: boolean } + Статус загрузки файла. Допустимые значения: + * `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: 'Произвольные метаданные (width/height для картинок и т.п.).' + description: | + Произвольные метаданные файла (например, размеры изображения). created_at: { type: string } user_id: type: string format: uuid - description: UUID юзера-загрузчика. + description: Идентификатор пользователя, загрузившего файл. chat_channel_id: type: string format: uuid nullable: true + description: | + Идентификатор канала чата, к которому привязан файл, + если применимо. FilePrepareUploadBody: type: object @@ -13061,33 +13133,42 @@ components: properties: name: type: string + description: Имя файла. example: "wallet_import.xlsx" ext: type: string + description: Расширение файла без точки. example: "xlsx" size: type: integer - description: Размер в байтах. + 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: integer - description: '`1` если файл должен быть доступен без авторизации (фото и т.п.), `0` — приватный.' - enum: [0, 1] - default: 0 + type: boolean + default: false + description: | + При значении `true` файл доступен без авторизации. + Используется, например, для изображений профиля. meta: type: object - description: 'Произвольные метаданные (например `{"width":100,"height":100}` для картинок).' + description: | + Произвольные метаданные файла. Например, для изображений + могут передаваться размеры: `{"width": 100, "height": 100}`. id: type: string format: uuid - description: 'Опционально. Если передать существующий `id`, вернётся та же запись (идемпотентность).' + description: | + Идентификатор существующей записи о файле. При передаче + возвращается имеющаяся запись (идемпотентное поведение). FilePrepareUploadResponse: type: object @@ -13097,15 +13178,16 @@ components: uploadInfo: type: object description: | - Информация для PUT-загрузки бинарника. Отсутствует, если файл - уже UPLOADED (повторный prepare-upload с тем же `id`). + Сведения для загрузки содержимого файла. Поле отсутствует + в ответе, если файл уже находится в статусе UPLOADED. properties: signedUrl: type: string format: uri description: | - Временный URL для PUT-запроса с телом-бинарником. - Не требует HRBox-авторизации — это прямая ссылка на S3. + Временная подписанная ссылка для PUT-запроса с содержимым + файла. Запрос выполняется напрямую в объектное хранилище + и не требует авторизации HRBox. UserApiTokenStatus: type: integer -- 2.54.0