From 5899525ca5f5db4995e5590400668dbcfc1a439d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 10 Oct 2026 02:02:24 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20README=20=D0=B8=20CLAUDE.md=20=E2=80=94?= =?UTF-8?q?=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D1=8B=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE?= =?UTF-8?q?=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D0=B8,=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20(#96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- CLAUDE.md | 4 ++-- README.md | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7ae139e..8cd038f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ `OUTBOX_MAX_REPUBLISH`=3, счётчик — `recognition_job.metrics.republish_count`) → `dlq`; дедуп — `request_msg_id` / `result_msg_id`. Статусы задания в Срезе 0 `audit_event` не пишут. - **Первичные документы (Срез 2c)** — `services/primary_files.py` (загрузка `upload_one`: дедуп по `(ЮЛ, sha256)` под - advisory-lock, SAVEPOINT на файл, `delete`), `services/file_status.py` (единственное место смены `stored_file.status` + advisory-lock, SAVEPOINT на файл, `delete`), v5.18 (#96): загрузка в консолидацию — `check_consolidation_for_upload` → `accept_uploads` (`endpoints/files/upload.py`, общий для обоих маршрутов) → `after_upload_to_consolidation` (отдельная транзакция, версии консолидации `INITIATED` с ЮЛ в периметре под `FOR UPDATE ORDER BY id`); рабочий стол — `page_desk` / `files_repo.list_desk_files` (`OR EXISTS` по `consolidation_document`); автозамена наследует `consolidation_id`; `services/file_status.py` (единственное место смены `stored_file.status` по ТЕКУЩЕМУ заданию; `PROCESSED`/`SUPERSEDED` обменом не перезаписываются), `services/result_mapping.py` (цикл маппинга: `pick_candidate` → проверка ключа результата (OQ-70) → чтение S3 вне транзакции → `map_file` в одной транзакции → автозамена `FILE_REPLACE`), `services/documents.py` (карточки, `confirm`), `services/auto_confirm.py` (автоподтверждение чистых документов в транзакции маппинга: `_map_candidate`: `map_file` → автозамена → `auto_confirm` → `on_documents_mapped`; `confirmed_by` NULL, `DOCUMENT_CONFIRM` без пользователя, переход версии — от `uploaded_by`; OQ-157), `domain/osv_mapping.py` (чистые функции: @@ -129,7 +129,7 @@ (`counterparty_id` + `counterparty_match_kind` + алиас); (5) ключ замены привязки — «тип, счёт, квартал, нарастающий» (R-1); ОСВ за Q1 равнозначна нарастающей и квартальной (R-14, Q1-эквивалентность — только для ОСВ, ББ/ОФР за Q1 версиям Q2–Q4 не подходят, R-14a); (6) «строка версии» для ручного сопоставления/автоприменения — только строки свода (R-11); (7) 404 — - код `NOT_FOUND`, различие по тексту; правки — `ensure_version_editable` (`VERSION_NOT_EDITABLE`/`LOCK_REQUIRED`/ + код `NOT_FOUND`, различие по тексту (в т. ч. «консолидация не найдена»); правки — `ensure_version_editable` (`VERSION_NOT_EDITABLE`/`LOCK_REQUIRED`/ `LOCKED_BY_OTHER` без `locked_by_name`). Деньги в ответах — строки с двумя знаками. Аудит: `CONSOLIDATION_CREATE`, `CONSOLIDATION_STATUS_CHANGE`, `CONSOLIDATION_LOCK`, `CONSOLIDATION_LOCK_TAKEOVER`, `MEMBERSHIP_CHANGE`, `OSV_COUNTERPARTY_MAP`, `LEGAL_ENTITY_CHANGE`, `EXPORT`; `FILE_REPLACE.unbound_version_ids`. Контракт для фронта — diff --git a/README.md b/README.md index 7c9a9bb..f7fd220 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,7 @@ cd web && npm install && npm run start # фронтенд (rsbuild dev), о ``` POST /legal-entities/{id}/files → S3 uploads/… + stored_file(UPLOADED) + recognition_job(queued) + audit FILE_UPLOAD + (`consolidation_id` NULL; загрузка в консолидацию — маршрут консолидации) → публикатор (outbox) → NATS files.tabular_uploaded → tabular-parser (читает S3, пишет results/…) → files.tabular_parsed → inbox (задание done, файл PROCESSING) → цикл маппинга (document + osv_entry, файл PROCESSED) → автозамена прежних версий (SUPERSEDED) → GET /files/{id}, GET /documents/{id}/entries → POST /documents/{id}/confirm (только для NEEDS_CHECK и форм после правки) @@ -526,6 +527,8 @@ S3 (storage); без S3 — предупреждение в логе, цикл |---|---|---| | `POST /legal-entities/{legal_entity_id}/files` | `DOCUMENT_EDIT` | загрузка файлов (multipart `files`), ответ `{accepted[], rejected[]}` | | `GET /legal-entities/{legal_entity_id}/files` | `DOCUMENT_VIEW` | файлы ЮЛ, новые первыми (`limit`/`offset`; `include_superseded=true` — с заменёнными) | +| `POST /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | `DOCUMENT_EDIT` | загрузка в консолидацию (v5.18): файл получает `consolidation_id`; версии консолидации «в работе» с ЮЛ в периметре `INITIATED` → `DOCUMENTS_PROCESSING` | +| `GET /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | `DOCUMENT_VIEW` | рабочий стол консолидации для ЮЛ: файлы с её `consolidation_id` + файлы с документами, привязанными к её версиям (`limit`/`offset`/`include_superseded`) | | `GET /files/{file_id}` | `DOCUMENT_VIEW` | карточка файла: статус, `job` (`error_code`, `error_message`, `mapping_error`, `warnings`, `mapping_warnings`), документы | | `DELETE /files/{file_id}` | `DOCUMENT_EDIT` | удалить файл (см. «Удаление и замена»), 204 | | `GET /documents/{document_id}` | `DOCUMENT_VIEW` | документ: статус, проверки, `locator`, `group_schema` листа, `warnings`, `file_id` | @@ -616,7 +619,7 @@ advisory-блокировкой. - Аудит обмена (смена статусов задания/файла в inbox, вахтёре, цикле маппинга) не пишется — `ASSUMPTION(OQ-55)`; `audit_event` пишут только загрузка, автозамена, удаление, подтверждение. - Объекты S3 при удалении не стираются (OQ-22, OQ-111): растёт бакет; e2e оставляет ~2 объекта за прогон. -- Общий размер multipart-запроса `POST /legal-entities/{id}/files` и число файлов в нём приложением не ограничены +- Общий размер multipart-запроса `POST /legal-entities/{id}/files` (и маршрута консолидации) и число файлов в нём приложением не ограничены (Starlette спулит части на диск, `UPLOAD_MAX_BYTES` — лимит одного файла); лимит запроса задаётся ingress платформы. - Маппер Среза 2c разбирал только листы ОСВ (`osvDocument`); с Среза 3b он мапит и формы ББ/ОФР (раздел «Формы ББ/ОФР»). Документы `DISCLOSURE`/`OTHER` результата не игнорируются: на каждый тип создаётся один документ без данных (`RECOGNIZED` при маппинге, затем автоподтверждение; @@ -663,7 +666,7 @@ advisory-блокировкой. | `GET /consolidations/versions/{id}/osv/export` | VIEW | xlsx: лист «Сводная ОСВ» и лист «Пробелы» | | `GET /consolidations/versions/{id}/counterparty-candidates` | VIEW | кандидаты для сопоставления: `scope=group\|all`, `q`, `entry_id` | | `POST /consolidations/versions/{id}/osv/entries/{entry_id}/counterparty` | EDIT | ручное сопоставление контрагента строки свода | -| `POST /legal-entities/{id}/files?consolidation_version_id=` | `DOCUMENT_EDIT` | загрузка в контексте версии (необязательный параметр, Срез 2c) | +| `POST /consolidations/{id}/legal-entities/{le}/files` | `DOCUMENT_EDIT` | загрузка в контексте консолидации (v5.18); query `consolidation_version_id` у маршрута ЮЛ удалён (v5.18) | Деньги в ответах — строки JSON с двумя знаками (`"100.00"`); версия любого статуса (включая `CANCELLED`) читается. @@ -678,7 +681,7 @@ advisory-блокировкой. | Событие | Переход | `changed_by` | |---|---|---| -| загрузка с `consolidation_version_id`, принят ≥ 1 файл | `INITIATED` → `DOCUMENTS_PROCESSING` | загрузивший | +| загрузка в консолидацию, принят ≥ 1 файл | `INITIATED` → `DOCUMENTS_PROCESSING` у каждой версии консолидации «в работе» с ЮЛ в периметре | загрузивший | | маппинг создал документ `NEEDS_CHECK`, подходящий версии | `DOCUMENTS_PROCESSING` → `DATA_CORRECTION` | `uploaded_by` файла (нет — переход пропускается, лог) | | подтверждение документа, привязанного к версии | `INITIATED`/`DOCUMENTS_PROCESSING` → `DATA_CORRECTION` | подтвердивший | | `POST …/status {CANCELLED, comment}` | любой «в работе» → `CANCELLED` (снимает блокировку) | пользователь | @@ -822,7 +825,7 @@ email без учёта регистра проверяет только сер | Код | HTTP | Где | |---|---|---| -| `NOT_FOUND` | 404 | объекта нет или он вне видимости; в консолидациях различие — по тексту: «версия консолидации не найдена», «ГК не найдена», «ГСК/ГСЗ не найдена», «строка ОСВ не найдена в версии», «ЮЛ не найдено» | +| `NOT_FOUND` | 404 | объекта нет или он вне видимости; в консолидациях различие — по тексту: «версия консолидации не найдена», «консолидация не найдена», «ГК не найдена», «ГСК/ГСЗ не найдена», «строка ОСВ не найдена в версии», «ЮЛ не найдено» | | `OFFICE_NOT_FOUND` | 422 | филиал из тела запроса не существует (ГК, ЮЛ, пользователь) | | `OFFICE_NOT_ALLOWED` | 422 | ГК можно закрепить только за своим филиалом | | `INVALID_INN` | 422 | ИНН не из 10 (ЮЛ) / 12 (ИП) цифр; в правке формы (`PATCH …/statement`) — не из 10 или 12 цифр | @@ -869,10 +872,11 @@ email без учёта регистра проверяет только сер | `PERIMETER_TOO_SMALL` | 422 | в ГСК (при создании без списка), в выбранных ЮЛ (`legal_entity_ids` при создании) или в новом периметре (`PUT …/members`) меньше двух ЮЛ; ничего не создаётся и не меняется | | `RELATED_GROUP_MISMATCH` | 422 | ГСК принадлежит другой ГК или имеет вид `gsz` (невидимая/неизвестная ГСК — 404 `NOT_FOUND`) | | `ENTITY_NOT_IN_GROUP` | 422 | `POST /consolidations` с `legal_entity_ids` и `PUT …/members`: ЮЛ не из ГК консолидации, без ГК или несуществующее (`legal_entity_ids` — перечень); проверка раньше `PERIMETER_TOO_SMALL` | -| `ENTITY_NOT_IN_PERIMETER` | 422 | загрузка с `consolidation_version_id`: ЮЛ файла не входит в периметр версии | +| `ENTITY_NOT_IN_PERIMETER` | 422 | загрузка в консолидацию: ЮЛ не входит в периметр ни одной версии «в работе» | +| `CONSOLIDATION_NOT_EDITABLE` | 409 | загрузка в консолидацию без версии «в работе» | | `QUERY_TOO_SHORT` | 422 | кандидаты `scope=all`: `q` короче 3 символов после обрезки | | `COMMENT_REQUIRED` | 422 | `POST …/status` в `CANCELLED` без комментария (из одних пробелов — тоже) | -| `VERSION_NOT_EDITABLE` | 409 | версия не «в работе» (правки, загрузка с версией) либо `CANCELLED` при «взять в работу» | +| `VERSION_NOT_EDITABLE` | 409 | версия не «в работе» (правки) либо `CANCELLED` при «взять в работу» | | `LOCK_REQUIRED` | 409 | правка версии без своей активной блокировки | | `LOCKED_BY_OTHER` | 409 | версию держит другой пользователь: из `POST …/lock` — `locked_by`, `locked_by_name`, `locked_at`; из правок (`ensure_version_editable`) — только `locked_by`, `locked_at`; гонка двух взятий (частичный UNIQUE) — то же 409 | | `NOT_LOCK_OWNER` | 409 | `DELETE …/lock`: блокировка чужая (`locked_by`, `locked_at`) |