17 KiB
directories-api Specification
Purpose
API справочников ядра: группы компаний (ГК), юридические лица и ИП с алиасами и историей, группы связанных компаний и
заёмщиков (ГСК/ГСЗ) с составом, филиалы. Все изменения идут через сервисы с историей (SCD4/SCD2) и записью в audit_event;
имена полей — как в текущей DBML (v5.18).
Requirements
Requirement: Филиалы доступны для чтения
GET /api/v1/directories/regional-offices SHALL возвращать список regional_office (id, name, is_head_office, is_active)
любому пользователю с правом DIRECTORY_VIEW; создание и изменение филиалов через API в Срезе 1 не предоставляется
(seed, импорт).
Scenario: Список филиалов
- WHEN пользователь с
DIRECTORY_VIEWзапрашивает список послеseed --dev - THEN ответ 200 содержит Головной офис с
is_head_office = trueи два dev-филиала
Requirement: Группа компаний — создание, чтение, изменение, архив, история
POST /api/v1/directories/company-groups (право DIRECTORY_EDIT) SHALL создавать ГК с полями name (обязательно),
crm_id (необязательно, уникально), base_regional_office_id (необязательно), генерируя code вида GK-NNNNNN и
valid_from = now, с записью audit_event(GROUP_CHANGE). PATCH …/{id} SHALL изменять name, crm_id,
base_regional_office_id, сохраняя снимок прежних значений в company_group_history (valid_from прежний,
valid_to = now) и обновляя valid_from; смена филиала пишет ASSIGNMENT_CHANGE, остальное — GROUP_CHANGE.
POST …/{id}/archive и …/unarchive SHALL переключать is_archived. GET … SHALL отдавать список с поиском по
name/crm_id/code (подстрока без регистра), фильтром is_archived, пагинацией {items, total, limit, offset}
(limit ≤ 200) и счётчиком legal_entities_count; GET …/{id} — карточку; GET …/{id}/history — снимки по убыванию valid_from.
Удаление ГК API NOT предоставляет.
Scenario: Создание и код
- WHEN создаются две ГК подряд
- THEN их
codeразличаются, имеют видGK-и шесть цифр,valid_fromзаполнен, вaudit_eventдве записиGROUP_CHANGEсafter_value
Scenario: Перезакрепление за филиалом
- WHEN у ГК меняется
base_regional_office_id - THEN в
company_group_historyпоявляется строка с прежним филиалом иvalid_to, у ГК новыйvalid_from, записанaudit_event(ASSIGNMENT_CHANGE)сbefore_value/after_value
Scenario: Дубликат crm_id
- WHEN создаётся ГК с
crm_id, который уже есть - THEN ответ 409
{"detail": {"code": "CRM_ID_EXISTS", "message": …}}, записи нет
Requirement: Юридическое лицо / ИП — создание, чтение, изменение
POST /api/v1/directories/legal-entities (DIRECTORY_EDIT) SHALL создавать запись с inn (ЮЛ — ровно 10 цифр, ИП — 12;
иначе 422 INVALID_INN), entity_type (ЮЛ | ИП), name, company_group_id (NULL = монозаёмщик), regional_office_id
(по умолчанию — филиал ГК), crm_id, kpp; inn уникален (409 INN_EXISTS). kpp (DBML v5.17, 05-domain-rules.md §13.5)
необязателен у ЮЛ и запрещён у ИП; нормализуется (пробелы убираются, буквы — в верхний регистр) и SHALL быть 9 символов
[0-9]{4}[0-9A-Z]{2}[0-9]{3}, иначе 422 INVALID_KPP; пустая строка — «КПП нет»; уникальности у КПП нет.
PATCH …/{id} SHALL менять name, entity_type, crm_id, regional_office_id, kpp (kpp: null очищает КПП; смена типа
на ИП при непустом итоговом КПП — 422 INVALID_KPP) со снимком в legal_entity_history(event_type = UPDATE) и audit_event(LEGAL_ENTITY_CHANGE).
GET … SHALL давать список с фильтрами company_group_id, outside_group (company_group_id IS NULL), inn
(точно), q (подстрока имени/ИНН/КПП), closed (по close_date), пагинацией; GET …/{id} — карточку с текущими
членствами ГСК/ГСЗ и алиасами; GET …/{id}/history — снимки. В списке и карточке SHALL быть documents_count — число
документов файлов ЮЛ, кроме файлов SUPERSEDED. Во всех ответах ЮЛ, кроме снимков истории, SHALL быть company_group_name —
текущее имя ГК company_group_id (архивной тоже) или null у монозаёмщика; имена набора ЮЛ — одним запросом.
Ответ POST и PATCH — карточка (LegalEntityDetail: поля списка + memberships, aliases); поля формы
aliases, gsk_id, gsz_id — требование «Форма ЮЛ одним запросом».
Scenario: Неверная длина ИНН
- WHEN создаётся ИП с ИНН из 10 цифр
- THEN ответ 422 с кодом
INVALID_INN, запись не создана
Scenario: КПП у ИП
- WHEN создаётся ИП с
kpp = "770101001"или ЮЛ с КПП из 8 символов - THEN ответ 422 с кодом
INVALID_KPP, запись не создана
Scenario: Очистка КПП
- WHEN у ЮЛ с КПП выполняется
PATCHс{"kpp": null} - THEN
kpp= NULL, прежнее значение — в снимкеlegal_entity_history(event_type = UPDATE)и вaudit_event
Scenario: Монозаёмщик
- WHEN ЮЛ создано без
company_group_id - THEN оно отдаётся в списке с
outside_group = trueи видно пользователям филиалаregional_office_id
Scenario: Число документов
- WHEN у монозаёмщика файл с книгой НПЗ (ББ и ОФР) и заменённый файл с одной ОСВ
- THEN
documents_count = 2
Requirement: Перевод ЮЛ в другую ГК и вывод из состава
POST /api/v1/directories/legal-entities/{id}/move (DIRECTORY_EDIT; тело company_group_id, effective_date ≥ сегодня)
SHALL сменить ГК, записать legal_entity_history(event_type = MOVE_TO_GROUP, effective_date), закрыть все текущие
членства ЮЛ в ГСК/ГСЗ прежней ГК (valid_to = now, is_current = false) и записать LEGAL_ENTITY_CHANGE и
MEMBERSHIP_CHANGE. POST …/{id}/withdraw (тело event: CLOSE | REMOVE_FROM_GROUP, effective_date, comment)
SHALL установить company_group_id = NULL, при CLOSE — ещё close_date = effective_date, закрыть членства, записать
историю и аудит. effective_date раньше сегодняшнего дня → 422.
Scenario: Перевод закрывает членства
- WHEN ЮЛ состоит в ГСК старой ГК и переводится в новую ГК
- THEN членство получает
is_current = falseиvalid_to, ЮЛ имеет новуюcompany_group_id, в истории строкаMOVE_TO_GROUPс датой перевода
Scenario: Закрытие
- WHEN выполнен
withdrawсevent = CLOSE - THEN
close_dateзаполнен,company_group_id = NULL, ЮЛ отдаётся в списке сclosed = true, в историиCLOSE
Requirement: Алиасы ЮЛ
POST /api/v1/directories/legal-entities/{id}/aliases (DIRECTORY_EDIT; тело alias) SHALL сохранять alias как есть и
normalized_alias по §3 (нижний регистр, trim, без пунктуации и кавычек, без ОПФ ООО/АО/ПАО/ЗАО/ИП, схлопнутые пробелы);
повтор для того же ЮЛ — 409 ALIAS_EXISTS; такой же normalized_alias у другого ЮЛ — 409 ALIAS_TAKEN с указанием
legal_entity_id владельца. DELETE …/aliases/{alias_id} SHALL удалять алиас. Оба пишут LEGAL_ENTITY_CHANGE.
Scenario: Нормализация
- WHEN добавляется алиас
ООО «Пекарня Счастливочная» - THEN
normalized_alias = "пекарня счастливочная", повторное добавлениеПекарня Счастливочная, ООО→ 409ALIAS_EXISTS
Requirement: Форма ЮЛ одним запросом
POST /api/v1/directories/legal-entities и PATCH …/{id} SHALL принимать поля формы (макет 51-02, 79): aliases
(список строк 1–500 символов, не больше 100 элементов; больше — 422 массивом), gsk_id, gsz_id — и применять их в той
же транзакции, что и поля ЮЛ; ошибка на любом шаге откатывает весь запрос. Шаги и порядок ошибок: поля ЮЛ → алиасы → ГСК →
ГСЗ. Алиасы и членства SHALL проверяться и аудироваться так же, как в ручках …/aliases и …/related-groups/{id}/members
(EMPTY_ALIAS, ALIAS_TAKEN, DIFFERENT_COMPANY_GROUP, MEMBERSHIP_CHANGE, LEGAL_ENTITY_CHANGE по алиасу); два
элемента aliases с одним normalized_alias — 409 ALIAS_EXISTS. Группа SHALL быть видимой (иначе 404, как
несуществующая) и нужного вида: в gsk_id — ГСК, в gsz_id — ГСЗ (иначе 422 WRONG_GROUP_KIND).
В POST не переданное поле — пустой список / без группы. В PATCH не переданное поле SHALL не меняться; aliases: null
— 422; aliases — итоговый набор: алиасы сверяются по normalized_alias, совпавшие не трогаются (написание прежнее),
недостающие добавляются, лишние удаляются — удаление требует права DIRECTORY_DELETE (иначе 403
«недостаточно прав: требуется DIRECTORY_DELETE», ничего не меняется); gsk_id/gsz_id — итоговая группа вида: null
закрывает текущее членство вида, та же группа — без изменений, другая — текущее закрывается, новое открывается. Снимок
legal_entity_history(UPDATE) пишется только при изменении полей ЮЛ.
Scenario: Создание с алиасами и группами
- WHEN ЮЛ ГК создаётся с
aliases = ["ООО «Ромашка»", "Ромашка-Юг"],gsk_idиgsz_idгрупп своей ГК - THEN ответ 201 с двумя алиасами и двумя текущими членствами; в аудите — создание ЮЛ, два события по алиасам и два
MEMBERSHIP_CHANGE
Scenario: Чужой алиас откатывает создание
- WHEN в
aliasesесть алиас, закреплённый за другим ЮЛ - THEN ответ 409
ALIAS_TAKENсlegal_entity_idвладельца; ни ЮЛ, ни алиасов, ни членств, ни событий аудита
Scenario: Итоговый набор алиасов
- WHEN у ЮЛ алиасы
ООО «Альфа»иБета, аPATCHадминистратора передаётaliases = ["альфа", "Гамма"] - THEN
ООО «Альфа»остаётся с прежнимidи написанием,Бетаудалён,Гаммадобавлен; снимка истории нет
Scenario: Удаление алиаса без права
- WHEN пользователь без
DIRECTORY_DELETEпередаётaliasesбез одного из текущих алиасов - THEN ответ 403, ни поля ЮЛ, ни алиасы не меняются
Scenario: Смена ГСК и выход из ГСЗ
- WHEN ЮЛ в ГСК A и ГСЗ C;
PATCHсgsk_id = B,gsz_id = null - THEN членства в A и C закрыты, открыто членство в B; повтор того же тела ничего не меняет
Requirement: ГСК и ГСЗ — группы и состав
POST /api/v1/directories/company-groups/{gk_id}/related-groups (DIRECTORY_EDIT; тело kind ∈ {gsk, gsz}, name,
created_date) SHALL создавать related_group с code GSK-NNNNNN/GSZ-NNNNNN; PATCH …/related-groups/{id} — менять
name, created_date; DELETE …/related-groups/{id} (DIRECTORY_DELETE) SHALL удалять только группу без текущих
членов (иначе 409 GROUP_NOT_EMPTY). POST …/related-groups/{id}/members (тело legal_entity_id) SHALL добавлять текущее
членство с kind группы, valid_from = now, is_current = true; ЮЛ MUST принадлежать той же ГК (иначе 422
DIFFERENT_COMPANY_GROUP); если у ЮЛ уже есть текущее членство в группе того же вида — 409 MEMBER_OF_OTHER_GROUP
(с related_group_id); повтор в той же группе — 409 MEMBER_EXISTS. DELETE …/members/{legal_entity_id} SHALL закрывать
членство (valid_to, is_current = false). Все операции пишут MEMBERSHIP_CHANGE. GET …/related-groups и
GET …/related-groups/{id} SHALL отдавать группы с числом текущих членов и списком членов.
Scenario: Одновременно в ГСК и ГСЗ
- WHEN ЮЛ добавлено в ГСК своей ГК, затем в ГСЗ той же ГК
- THEN обе операции успешны, у ЮЛ два текущих членства с
kind = gskиkind = gsz
Scenario: Вторая ГСК запрещена
- WHEN ЮЛ с текущим членством в ГСК A добавляют в ГСК B той же ГК
- THEN ответ 409
MEMBER_OF_OTHER_GROUPс идентификатором ГСК A; данные не меняются
Scenario: Повторное включение после исключения
- WHEN членство закрыто, затем ЮЛ снова добавляют в ту же группу
- THEN появляется новая строка
related_group_memberсis_current = true, старая остаётся сvalid_to
Scenario: Удаление непустой группы
- WHEN удаляется ГСК с текущими членами
- THEN ответ 409
GROUP_NOT_EMPTY
Requirement: Права и аудит на всех маршрутах справочников
Каждый маршрут SHALL требовать соответствующее право (DIRECTORY_VIEW для чтения, DIRECTORY_EDIT для изменений,
DIRECTORY_DELETE для удаления ГСК/ГСЗ и алиасов) и применять политику видимости (directory-policy); каждая мутация
SHALL выполняться в одной транзакции с записью audit_event (user_id инициатора, before_value/after_value,
ip_address, user_agent), откат транзакции отменяет и аудит.
Scenario: Нет права
- WHEN пользователь с ролью
EXPERT(толькоDIRECTORY_VIEW) создаёт ГК - THEN ответ 403, записи и аудита нет
Scenario: Ошибка внутри мутации
- WHEN при переводе ЮЛ закрытие членства завершается ошибкой БД
- THEN ЮЛ остаётся в прежней ГК, строк истории и аудита нет