Партнерский API (#19245)¶
Документация конечных точек API Softera, реализованных/расширенных в рамках тикета #19245: создание партнерской организации, поиск организаций, CRUD для местоположений каталога LOCCAT, изменение GLN организации.
Общие положения¶
- Базовый URL:
https://api-test.docura.net/ap1/partner/api/ - Аутентификация: заголовок
X-Authorization: <api_key>(токен пользователя, полеapi_keyв таблицеusers). - Заголовок
X-App-Keyне используется клиентами — на бэкенде эта проверка в настоящее время не подключена ни к одной цепочке фильтров (мертвый код с 2023 года), поэтому его можно опустить. - Формат — JSON (если явно не указано иное).
- Роли (полномочия):
SYSTEM_ADMIN,SYSTEM_SUPPORT,CLIENT_ADMIN,CLIENT_EDITOR,PROVIDER_ADMIN— конкретные разрешенные роли см. в описании каждой конечной точки.
1. Создание партнерской организации¶
POST /user/organization¶
Создает новую организацию и добавляет вызывающего пользователя в нее в качестве CLIENT_ADMIN (через отдельный, более ранний шаг в конвейере создания).
Права: без ограничений по ролям — доступно любому аутентифицированному пользователю. Права выражаются не через роль вызывающего пользователя, а через то, к какой организации принадлежит сам вызывающий пользователь (см. логику operatorId ниже).
Тело запроса:
| Поле | Тип | Обязательно | Ограничения |
|---|---|---|---|
| taxId | string | да | 2–20 символов |
| registrationNumber | string | да | 2–50 символов |
| name | string | да | 2–50 символов |
| countryCode | string | да | 2 символа |
| locale | string | да | 2 символа, должно существовать в справочнике локалей |
| errorEmail | string | нет | до 250 символов, формат email |
| notes | string | нет | до 255 символов |
| phones | string | нет | до 50 символов |
| string | нет | до 150 символов, формат email |
Поле integrationProfileType, которое появилось в более ранних итерациях этой функции в рамках того же тикета, было удалено из финальной версии — клиент не может явно выбирать профиль/connectionType (см. ниже).
Ответ (200): тот же набор полей + id (Long), iln (сгенерированный GLN организации).
Ошибки:
-
409 Conflict(organization.create.conflict.tax_id) — организация с такимtaxIdуже существует. -
400— недопустимые поля тела запроса (валидация бинов) или локаль не найдена (not.exists.specified.organization.locale).
Логика определения оператора (operatorId) новой организации¶
Рассчитывается из организации самого ВЫЗЫВАЮЩЕГО пользователя (callerIln — ILN текущей организации сессии), без явных параметров в запросе:
- Если организация вызывающего — сама softera (
profile.softeraOrganizationIln) → оператор новой организации = сама softera. Дополнительно, в этом случае в новую организацию добавляетсяCLIENT_ADMIN'ом дефолтный пользователь softera (profile.softeraDefaultUserId, если задан). - Если организация вызывающего — провайдер (
role='P', но не softera) → оператор = собственная организация вызывающего. - Иначе (ни softera, ни провайдер) → оператор = организация из
profile.defaultOperatorOrganizationIln(fallback на «оператора по умолчанию»). - Если ни организация вызывающего, ни fallback-организация не найдены по ILN —
500(IllegalStateException, в норме такого быть не должно).
Логика connectionType для новой организации¶
- Вызывающий — softera → всегда
Payment(жестко задано, независимо от чего-либо). - В противном случае →
connectionType=connectionTypeорганизации-оператора (рассчитанное выше — поставщик или резервная организация). Клиент не может явно указатьconnectionType/профиль интеграции.
2. Поиск организаций¶
GET /organizations/search?query=<string>¶
Права: SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN. Нет ограничения по operatorId — провайдер видит результаты по всей базе организаций, а не только по своим клиентам (сознательное решение, принятое в рамках этого тикета).
Параметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
| query | string | да | Строка поиска. Пустая/отсутствующая строка → пустой список без ошибки. |
Логика поиска в БД — одиночное OR по организации:
- точное совпадение по
id(как текст); - точное совпадение по
iln(GLN); - точное совпадение по
regNr(без учета регистра); - точное совпадение по
vatNr(без учета регистра); - подстрока (
ILIKE) вnameорганизации; - подстрока (
ILIKE) вnameлюбой из ее локаций LOCCAT.
Ответ (200): массив, каждый элемент обернут в объект organization:
[
{
"organization": {
"id": 100099815,
"name": "New brand sof",
"iln": "2000005810008",
"taxId": "...",
"registrationNumber": "...",
"countryCode": "EE",
"locations": [ { "id": ..., "iln": "...", "name": "...", "formattedAddress": "...", ... } ]
}
}
]
Важный нюанс: locations[] содержит только те локации, чье собственное имя совпало со строкой поиска — не все локации найденной организации. Если организация была найдена по id/iln/regNr/vatNr/name, а не по имени локации, locations будет пустым массивом. Это преднамеренное поведение, а не ошибка.
3. Адреса LOCCAT организации¶
Базовый путь: /organization/{gln}/locations, где {gln} — это GLN организации (не числовой идентификатор).
Права (общие для всех трёх методов ниже):
-
SYSTEM_ADMIN/SYSTEM_SUPPORT— без ограничений. -
PROVIDER_ADMIN— только если организация вызывающего =operatorIdцелевой организации (провайдер сам управляет местоположениями организаций, которые он обслуживает). - Другие роли —
403(not.have.access). - Организация не найдена по GLN →
400(organization.not.exists).
3.1 POST /organization/{gln}/locations — создание/обновление локации¶
Логика Upsert:
- Если
idпередан в теле — явное обновление существующей локации по её внутреннему id (она должна принадлежать указанной организации, иначе404). - Если
idне передан, но переданiln— ищется существующая локация организации с такимiln; если найдена, обновляется, иначе создается новая. - Если
ilnтакже не передан (или "пуст" поIlnHelper.isEmpty) — GLN локации генерируется автоматически (см. ниже).
Тело запроса (LoccatLocationWithIdData — LoccatLocationData + необязательный id):
| Поле | Тип | Обязательно | Ограничения |
|---|---|---|---|
| id | Long | нет | для явного обновления |
| iln | string | нет | до 13 символов; если отсутствует — генерируется автоматически |
| codeBySender | string | да | до 70 символов |
| codeByReceiver | string | нет | до 70 символов |
| name | string | да | до 175 символов |
| legal | boolean | нет | флаг "юридический адрес" |
| formattedAddress | string | нет | до 300 символов |
| streetAndNumber | string | нет | до 140 символов |
| streetAndNumber2 | string | нет | до 140 символов |
| city | string | нет | до 175 символов |
| state | string | нет | до 175 символов |
| postalCode | string | нет | до 9 символов |
| countryCode | string | нет | до 2 символов |
| latitude / longitude | double | нет | |
| remarks | string | нет | до 350 символов |
| phoneNumber | string | нет | до 175 символов |
| string | нет | до 350 символов, формат email | |
| web | string | нет | до 175 символов |
Ответ (200): то же тело + id созданной/обновленной локации.
Автогенерация GLN местоположения (когда iln не передаётся)¶
Он повторно использует устаревший алгоритм UserWebController.getNextLocalizationCatalogGln (ядро):
- Если у организации уже есть местоположения (найден
maxIln) → следующий GLN = тело последнего сгенерированного GLN + 1. - Если местоположений нет, и организация является тестовой (
isTest) → GLN строится изglnPrefixорганизации, дополненного нулями. - Если местоположений нет, организация не является тестовой, и нет «юридического» местоположения → возвращается собственный GLN организации.
- Если есть юридическое местоположение, но нет других (крайний случай) → GLN строится из
glnPrefix.
Кроме того (изменение из этого тикета): если GLN, сгенерированный таким образом, совпадает с собственным GLN организации (ветви 2 и 3 выше) — он увеличивается на 1, чтобы никогда не дублировать GLN организации.
Известные ограничения (принятые как есть, без дополнительной защиты):
- Отсутствует транзакционная блокировка между «получением следующего GLN» и «созданием местоположения с ним» — при параллельных запросах без
ilnвозможна гонка и400 duplicate.line.iln. - Увеличение при переполнении (тело GLN =
999999999999) теоретически может привести к GLN на один символ длиннее обычного — крайне маловероятный крайний случай, исправление не запрашивалось. - Флаг
legalНЕ устанавливается автоматически при создании первого местоположения безiln— если у организации нет юридического местоположения, повторныйPOSTбезilnпопытается снова сгенерировать тот же GLN организации и завершится ошибкой400 duplicate.line.iln. Клиент должен либо явно передаватьilnво втором и последующих запросах, либо управлятьlegalсамостоятельно.
3.2 GET /organization/{gln}/locations — получить локации¶
Особенности: фильтр передается в теле GET-запроса (JSON); это не параметры запроса. Если тело пустое (Content-Length <= 0) — возвращаются все локации организации.
Тело запроса (необязательно):
| Поле | Тип | Описание |
|---|---|---|
| id | Long | точный поиск по ID локации (плюс необязательная проверка по GLN, если передан) |
| iln | string | фильтр по GLN |
| name | string | фильтр по имени (подстрока) |
| address | string | фильтр по адресу (подстрока) |
Приоритет: если передан id — возвращается (не более одной) локация по ID; иначе, если установлен хотя бы один из iln/name/address — отфильтрованный поиск; иначе — все локации организации.
Ответ (200): массив локаций (в том же формате, что и в POST-ответе).
3.3 DELETE /organization/{gln}/locations¶
Тело запроса (обязательно):
| Поле | Тип | Обязательно |
|---|---|---|
| id | Long | да |
Удаление только по внутреннему идентификатору местоположения; удаление по iln не реализовано (было рассмотрено и отменено в рамках данного тикета).
Ответ: 200 при успешном выполнении, 404, если местоположение с таким идентификатором не найдено в организации, 400 location.id.should.be.specified, если id не передан.
4. Изменение GLN организации¶
PATCH /organization/{organizationId}/gln?gln=<new GLN>¶
Примечание: здесь {organizationId} — это числовой внутренний идентификатор организации (в отличие от вышеуказанных конечных точек местоположения, которые принимают GLN в пути).
Права:
-
SYSTEM_ADMIN/SYSTEM_SUPPORT— без ограничений. -
PROVIDER_ADMIN— только если организация вызывающего абонента =operatorIdцелевой организации. - Остальные —
403 not.have.access. - Организация не найдена по идентификатору →
404(пустое тело).
Параметры:
| Параметр | Тип | Обязательный |
|---|---|---|
| gln | string | да, параметр запроса |
Что происходит внутри (повторно используется стандартный конвейер обновления организации, OrganizationUpdateEvent → OrganizationCommonUpdateHandler):
- Проверка формата GLN + контрольной цифры →
400в случае ошибки. - Проверка на дубликаты — GLN не должен принадлежать другой организации →
400 Duplicate ILN. - Старое значение сохраняется в
previousIln. - Если у организации есть «юридическое» местоположение LOCCAT — его
ilnавтоматически синхронизируется с новым GLN организации (loccatReconciliation). Если юридического местоположения нет — шаг пропускается без уведомления. - Стандартные шаги сверки обновления организации (партнер/условие/уведомления и т. д., не специфичные для этой функции).
Ответ (200): тело организации (OrganizationWithIdData) с уже примененным новым GLN.
5. Сводная таблица конечных точек¶
| Метод | Путь | Идентификатор пути | Роли |
|---|---|---|---|
| POST | /user/organization | — | любой аутентифицированный |
| GET | /organizations/search | — | SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN |
| POST | /organization/{gln}/locations | GLN | SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN (соответствие operatorId) |
| GET | /organization/{gln}/locations | GLN | SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN (соответствие operatorId) |
| DELETE | /organization/{gln}/locations | GLN | SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN (соответствие operatorId) |
| PATCH | /organization/{organizationId}/gln | числовой идентификатор | SYSTEM_ADMIN, SYSTEM_SUPPORT, PROVIDER_ADMIN (соответствие operatorId) |
6. Примеры¶
# Create an organisation
curl --location --request POST 'https://api-test.docura.net/ap1/partner/api/user/organization' \
--header 'X-Authorization: <token>' \
--header 'Content-Type: application/json' \
--data '{
"taxId": "EE100000001",
"registrationNumber": "12345678",
"name": "Example OU",
"countryCode": "EE",
"locale": "en"
}'
# Search organisations
curl --location 'https://api-test.docura.net/ap1/partner/api/organizations/search?query=Example' \
--header 'X-Authorization: <token>'
# Create a location without an explicit GLN (auto-generation)
curl --location --request POST 'https://api-test.docura.net/ap1/partner/api/organization/<org_gln>/locations' \
--header 'X-Authorization: <token>' \
--header 'Content-Type: application/json' \
--data '{
"name": "Warehouse 1",
"codeBySender": "WH1",
"countryCode": "EE"
}'
# Get all locations of an organisation
curl --location 'https://api-test.docura.net/ap1/partner/api/organization/<org_gln>/locations' \
--header 'X-Authorization: <token>'
# Delete a location by id
curl --location --request DELETE 'https://api-test.docura.net/ap1/partner/api/organization/<org_gln>/locations' \
--header 'X-Authorization: <token>' \
--header 'Content-Type: application/json' \
--data '{"id": 682021}'
# Change an organisation's GLN
curl --location --request PATCH 'https://api-test.docura.net/ap1/partner/api/organization/<numeric_id>/gln?gln=2000005819995' \
--header 'X-Authorization: <token>'
Source version: 3 · Status: fresh · Roman Startsev