Merge pull request 'Гайды: форма ЮЛ одним запросом (arm-core #84)' (#83) from feat/84-le-form-fields into main

Reviewed-on: #83
This commit is contained in:
aep 2026-10-09 13:03:12 +03:00
commit 530267e98f
2 changed files with 15 additions and 6 deletions

View File

@ -23,12 +23,13 @@
---
## 1. Что изменилось за последние дни (05–07.10.2026)
## 1. Что изменилось за последние дни (05–09.10.2026)
Если клиент писался по более ранним контрактам, пройдитесь по этому списку.
| Когда, PR | Что изменилось в API | Что сделать в клиенте |
|---|---|---|
| 09.10, arm-core #85 (#84) | **Форма ЮЛ одним запросом** (макет [51-02], [79]): `POST` и `PATCH /directories/legal-entities` принимают `aliases: string[]` (1–500 символов, ≤ 100), `gsk_id`, `gsz_id` и применяют их в той же транзакции, что и поля ЮЛ (ошибка — откат всего). `POST`: алиасы и членства создаются вместе с ЮЛ. `PATCH`: не переданное поле не меняется; `aliases` — **итоговый набор** (сверка по `normalized_alias`: совпавшие не трогаются, лишние удаляются — нужно право `DIRECTORY_DELETE`, иначе 403 строкой; `aliases: null` — 422), `gsk_id`/`gsz_id` — итоговая группа (`null` — выйти, другая — сменить). Новый код 422 `WRONG_GROUP_KIND`; дубль в списке — 409 `ALIAS_EXISTS`. Ответ обоих — **`LegalEntityDetail`** (было `LegalEntityOut`; поля прежние + `memberships`, `aliases`) | «Добавить»/«Сохранить» в модалках ЮЛ — один запрос со всей формой; в `PATCH` слать `aliases` и группы целиком, как они на экране; после ответа брать карточку из него, без `GET`; удаление тега-алиаса без `DIRECTORY_DELETE` (`/me.permissions`) не предлагать — сервер ответит 403 |
| 08.10, arm-core #75 (#74) | **Отмена П-1** (решение владельца 08.10.2026): «несопоставленная» строка свода — нет ЮЛ после резолва **и** нет ИНН из файла. `unmapped_total`, `GET …/osv?unmapped=true` и экспорт с этим фильтром больше не включают строки с ИНН, которого нет в справочнике (`unmapped=false` — все остальные строки). Новое поле `ConsolidatedRowOut.counterparty_entity_inn` — ИНН сопоставленного ЮЛ справочника (П-8; `counterparty_inn` — по-прежнему ИНН из файла) | «Указать» — только у строк без `counterparty_id` и без `counterparty_inn`; у строки, сопоставленной без ИНН в файле, показывать `counterparty_entity_inn` с возможностью пересопоставить (макет [32-osv-no-inn], `osv-inn-mapped-button`; повторный `POST …/counterparty` разрешён) |
| 08.10, arm-core #60 (#58) | **Предупреждение автозамены** (OQ-154): если новый файл заменил прежний целиком, а части документов прежнего в нём нет (книга «ББ + ОФР» → отдельный ОФР), в `job.mapping_warnings` **нового** файла приходит одна строка на заменённый файл: «автозамена: файл «…» (версия N) заменён этим файлом целиком, но в новом файле нет документов прежнего: …; привязки сняты с версий консолидации: …» — с пометкой `CONFIRMED` у подтверждённых документов. Схемы ответов не менялись; поведение замены прежнее | показать предупреждение в карточке нового файла заметно (не только в общем списке подсказок): аналитик должен догрузить выпавшие документы; текст не разбирать |
| 08.10, arm-core #59 (#57) | **Алиас приоритетнее имени** (OQ-128): при ЮЛ-тёзке по нормализованному имени строка с совпавшим алиасом теперь сопоставляется с владельцем алиаса (`match_kind = ALIAS`; в своде — `match_stored = false`, если найдено на лету). После ручного сопоставления `unmapped_total` может уменьшиться больше чем на 1 + `propagated` | после ручного сопоставления перечитывать и страницу, и `…/osv/summary` (как и раньше), не вычислять «Осталось N» на клиенте |
@ -281,8 +282,8 @@ UI прячет элементы по `GET /me → permissions` (объедин
| `POST /directories/company-groups` | EDIT | `{name (1–500), crm_id?, base_regional_office_id?}` | 201 `CompanyGroupOut` (код `GK-NNNNNN` назначает сервер) | 422 `OFFICE_NOT_FOUND`, 422 `OFFICE_NOT_ALLOWED` (филиал не свой), 409 `CRM_ID_EXISTS` |
| `PATCH /directories/company-groups/{group_id}` | EDIT | `{name?, crm_id?, base_regional_office_id?}` (частично) | `CompanyGroupOut` | те же |
| `POST /directories/company-groups/{group_id}/archive` · `/unarchive` | EDIT | — | `CompanyGroupOut` (повтор — без изменений) | — |
| `POST /directories/legal-entities` | EDIT | `{inn, entity_type, name, kpp?, company_group_id?, regional_office_id?, crm_id?}`; без `company_group_id` — монозаёмщик | 201 `LegalEntityOut` | 422 `INVALID_INN` (ЮЛ — 10 цифр, ИП — 12; пробелы внутри убираются), 422 `INVALID_KPP` (формат или КПП у ИП), 404 (ГК не видна), 409 `INN_EXISTS`, 409 `CRM_ID_EXISTS`, 422 `OFFICE_NOT_FOUND` / `OFFICE_NOT_ALLOWED` — в этом порядке |
| `PATCH /directories/legal-entities/{entity_id}` | EDIT | `{name?, entity_type?, crm_id?, regional_office_id?, kpp?}`; `kpp: null` очищает КПП; ИНН и ГК так не меняются | `LegalEntityOut` | 422 `INVALID_INN` (ИНН не подходит новому типу), 422 `INVALID_KPP` (формат; КПП у ИП, в т. ч. смена типа на ИП без очистки КПП), 422 `OFFICE_*`, 409 `CRM_ID_EXISTS`; `name`/`entity_type: null` — 422 массивом |
| `POST /directories/legal-entities` | EDIT | `{inn, entity_type, name, kpp?, company_group_id?, regional_office_id?, crm_id?, aliases?, gsk_id?, gsz_id?}`; без `company_group_id` — монозаёмщик (тогда без ГСК/ГСЗ); `aliases` — строки 1–500, ≤ 100 | 201 `LegalEntityDetail` (с `aliases`, `memberships`) | 422 `INVALID_INN` (ЮЛ — 10 цифр, ИП — 12; пробелы внутри убираются), 422 `INVALID_KPP` (формат или КПП у ИП), 404 (ГК не видна), 409 `INN_EXISTS`, 409 `CRM_ID_EXISTS`, 422 `OFFICE_NOT_FOUND` / `OFFICE_NOT_ALLOWED` — в этом порядке; затем алиасы: 422 `EMPTY_ALIAS`, 409 `ALIAS_EXISTS` (повтор в списке), 409 `ALIAS_TAKEN` (`legal_entity_id`); затем ГСК, ГСЗ: 404 (группа не видна), 422 `WRONG_GROUP_KIND`, 422 `DIFFERENT_COMPANY_GROUP`. Любая ошибка — ничего не создано |
| `PATCH /directories/legal-entities/{entity_id}` | EDIT | `{name?, entity_type?, crm_id?, regional_office_id?, kpp?, aliases?, gsk_id?, gsz_id?}`; `kpp: null` очищает КПП; ИНН и ГК так не меняются; `aliases` — итоговый набор (совпавшие по `normalized_alias` остаются как были, лишние удаляются, новые добавляются); `gsk_id`/`gsz_id` — итоговая группа, `null` — выйти; не переданное поле не меняется | `LegalEntityDetail` | 422 `INVALID_INN` (ИНН не подходит новому типу), 422 `INVALID_KPP` (формат; КПП у ИП, в т. ч. смена типа на ИП без очистки КПП), 422 `OFFICE_*`, 409 `CRM_ID_EXISTS`; `name`/`entity_type`/`aliases: null` — 422 массивом; алиасы и группы — коды как у `POST`; удаление алиаса без `DIRECTORY_DELETE` — 403 строкой «недостаточно прав: требуется DIRECTORY_DELETE». Любая ошибка — ничего не изменено |
| `POST /directories/legal-entities/{entity_id}/move` | EDIT | `{company_group_id, effective_date}` (дата ≥ сегодня по Москве) | `LegalEntityOut`; текущие членства ГСК/ГСЗ закрываются | 422 `EFFECTIVE_DATE_IN_PAST`, 404 (ГК назначения не видна), 409 `LEGAL_ENTITY_CLOSED`, 409 `SAME_COMPANY_GROUP` |
| `POST /directories/legal-entities/{entity_id}/withdraw` | EDIT | `{event: "CLOSE" \| "REMOVE_FROM_GROUP", effective_date, comment?}` | `LegalEntityOut` | 422 `EFFECTIVE_DATE_IN_PAST`, 409 `ALREADY_CLOSED`, 409 `NOT_IN_COMPANY_GROUP` |
| `POST /directories/legal-entities/{entity_id}/aliases` | EDIT | `{alias (1–500)}` — как в ОСВ | 201 `AliasOut {id, alias, normalized_alias, created_at}` | 422 `EMPTY_ALIAS`, 409 `ALIAS_EXISTS`, 409 `ALIAS_TAKEN` (`legal_entity_id` владельца) |
@ -481,6 +482,7 @@ UI прячет элементы по `GET /me → permissions` (объедин
| Старт приложения | токен → `GET /me` (профиль и права в контекст); `GET /directories/regional-offices` — только для выбора филиала в формах (кэш на сессию) |
| [03] Группы компаний | `GET /directories/company-groups?q=&is_archived=&limit=&offset=`; «Основной РФ» — `base_regional_office.name` |
| Карточка ГК / состав ГК | `GET /directories/company-groups/{id}`, `GET /directories/legal-entities?company_group_id=…`, `GET …/company-groups/{id}/related-groups`; правки — §5.3 |
| [51-02] Добавить ЮЛ / [79] Изменить ЮЛ | списки «Входит в ГСК/ГСЗ» — `GET …/company-groups/{id}/related-groups` (по `kind`); «Добавить» — `POST /directories/legal-entities` со всей формой (`aliases`, `gsk_id`, `gsz_id`); «Сохранить» — `PATCH …/{id}` с итоговыми `aliases` и группами; форму заполнять из `GET …/{id}` (`aliases[].alias`, `memberships[]` по `kind`) |
| [60] Монозаёмщики | `GET /directories/legal-entities?outside_group=true`; «Документы» — `documents_count`, статусы форм — `GET /legal-entities/{id}/files` по ЮЛ |
| [34] Консолидации ГК | `GET /company-groups/{id}/consolidations?in_work_only=`; клик по версии — `GET /consolidations/versions/{id}` (панель «Организации»: `members[]` — тип, ячейки ББ/ОФР, `osv_accounts`) |
| [42] Новая консолидация | ГК → ГСК (`…/related-groups`, `kind = gsk`) → все незакрытые ЮЛ ГК (`legal-entities?company_group_id=<ГК>&closed=false`, дочитывать по `offset`) с отметками членов ГСК (`related-groups/{id}` → `members[]`) → «Создать» (активна при ≥ 2 отмеченных) → `POST /consolidations` с `legal_entity_ids` |
@ -511,7 +513,7 @@ UI прячет элементы по `GET /me → permissions` (объедин
| Сопоставление контрагента | `row` (может быть `null`), `propagated` | `GET …/osv/summary`; страницу свода, если `propagated > 0` или `row = null` |
| Смена периметра / статуса | новый `VersionCard` | список версий ГК (`…/consolidations`), если он на экране |
| Взятие / снятие блокировки | `LockOut` / — | карточку версии (или обновить `lock` локально) |
| Правки справочника | новый объект | списки, где он показан; карточку ЮЛ после алиасов |
| Правки справочника | новый объект (у `POST`/`PATCH` ЮЛ — карточка с алиасами и членствами) | списки, где он показан; карточку ЮЛ — после отдельных ручек алиасов и состава ГСК/ГСЗ; состав ГСК/ГСЗ (`related-groups/{id}`), если он на экране, — после смены групп в форме ЮЛ |
---
@ -578,7 +580,8 @@ async function download(path: string, fallbackName: string): Promise<void> {
| `INVALID_KPP` | 422 | ЮЛ: формат КПП, КПП у ИП (в т. ч. смена типа на ИП); в импорте — код строки отчёта | — | ошибка поля «КПП» |
| `EFFECTIVE_DATE_IN_PAST` | 422 | перевод/вывод ЮЛ | — | ошибка поля «Дата» |
| `EMPTY_ALIAS` | 422 | алиас из одних ОПФ и знаков | — | ошибка поля |
| `DIFFERENT_COMPANY_GROUP` | 422 | член ГСК/ГСЗ из другой ГК | — | сообщение |
| `DIFFERENT_COMPANY_GROUP` | 422 | член ГСК/ГСЗ из другой ГК (в т. ч. ГСК/ГСЗ в форме монозаёмщика) | — | сообщение |
| `WRONG_GROUP_KIND` | 422 | форма ЮЛ: в `gsk_id` передана ГСЗ или в `gsz_id` — ГСК | — | ошибка поля «Входит в ГСК/ГСЗ» |
| `UNKNOWN_ROLE` / `UNKNOWN_PERMISSION` | 422 | администрирование | `roles` / `permissions` | подсветить коды |
| `MISSING_COLUMNS` | 422 | импорт | `missing[]` (поле есть и у `UNSUPPORTED_FILE` импорта — пустое) | перечислить колонки |
| `UNSUPPORTED_FILE` | 422 | импорт (файл целиком); загрузка — в `rejected[]` | — | сообщение |
@ -590,7 +593,7 @@ async function download(path: string, fallbackName: string): Promise<void> {
| `GROUP_NOT_EMPTY` | 409 | удаление ГСК/ГСЗ | — | «Сначала исключите членов» |
| `MEMBER_EXISTS` | 409 | ЮЛ уже в этой группе | — | сообщение |
| `MEMBER_OF_OTHER_GROUP` | 409 | ЮЛ уже в другой группе этого вида | `related_group_id` (не при гонке) | сообщение со ссылкой на группу |
| `ALIAS_EXISTS` | 409 | алиас у этого ЮЛ уже есть | — | сообщение |
| `ALIAS_EXISTS` | 409 | алиас у этого ЮЛ уже есть; в форме ЮЛ — два тега совпадают после нормализации | — | сообщение (в форме — подсветить повтор) |
| `ALIAS_TAKEN` | 409 | алиас у другого ЮЛ | справочник: `legal_entity_id`; сопоставление: `owners[] {legal_entity_id, name}` | в сопоставлении — предложить «Перенести алиас» (`move_alias: true`) |
| `LAST_ADMIN` | 409 | администрирование | — | «Нельзя оставить систему без администратора» |
| `ALREADY_CONFIRMED`, `FILE_SUPERSEDED`, `DOCUMENT_NOT_CONFIRMABLE` | 409 | подтверждение, правка формы | — | перечитать документ |

View File

@ -265,6 +265,12 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
| D22 | ka_go | `DELETE /directories/related-groups/{id пустой}` | 403 (право `DIRECTORY_DELETE` только у ADMIN — так задумано) |
| D23 | expert | любой `POST` в справочники | 403 |
| D24 | любой | `GET /directories/company-groups?limit=201` | 422 |
| D25 | ka_go | `POST /directories/legal-entities` в ГК из D14 с `"aliases":["ООО «Бета»","Бета-Юг"]`, `gsk_id` и `gsz_id` — ГСК и ГСЗ из D14 | 201, в ответе два `aliases` и два `memberships`; в журнале — создание ЮЛ, два события по алиасам и два `MEMBERSHIP_CHANGE` |
| D26 | ka_go | то же с новым ИНН, но в `aliases` алиас «Альфа» (занят в D12) | 409 `ALIAS_TAKEN`; ЮЛ с этим ИНН не появилось |
| D27 | ka_go | `POST` ЮЛ с `gsk_id` = id ГСЗ | 422 `WRONG_GROUP_KIND` |
| D28 | ka_go | `PATCH` ЮЛ из D25 `{"aliases":["ООО «Бета»","Гамма"]}` | 403 «недостаточно прав: требуется DIRECTORY_DELETE» (удаляется «Бета-Юг»); алиасы не изменились |
| D29 | admin | тот же `PATCH` | 200: «ООО «Бета»» с прежним `id`, «Бета-Юг» удалён, «Гамма» добавлен; истории ЮЛ (`…/history`) не прибавилось |
| D30 | ka_go | `PATCH` ЮЛ из D25 `{"gsz_id":null}`, затем повтор | 200, в `memberships` только ГСК; повтор — без новых событий в журнале |
### 6.3 Импорт из ЕЦП CRM (пользователь `admin`, curl или Swagger)