Пара «новый документ — прежний» с counterparty_check = MISMATCH у любого из двух в автозамене не участвует: сканы разных организаций в одной карточке больше не вытесняют друг друга. UNKNOWN заменяется как прежде. Если файл оставлен только из-за MISMATCH, в mapping_warnings нового файла — «автозамена не выполнена» с именем и версией оставленного файла. Спека primary-files дополнена. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
321 lines
33 KiB
Markdown
321 lines
33 KiB
Markdown
# primary-files Specification
|
||
|
||
## Purpose
|
||
Загрузка файлов первичной отчётности в ядро: физический файл в S3 и `stored_file`, задание распознавания в outbox,
|
||
дедуп, жизненный цикл статуса файла (§9.2), автозамена прежних версий и удаление.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Загрузка файлов организации
|
||
`POST /api/v1/legal-entities/{legal_entity_id}/files` (multipart, одно или несколько полей `files`; право
|
||
`DOCUMENT_EDIT`; ЮЛ в области видимости пользователя, иначе 404) SHALL для каждого файла в отдельной транзакции:
|
||
определить тип по расширению и сверить с сигнатурой содержимого (`xls` — BIFF8, `xlsx`/`xlsb` — zip, `pdf`, `png`,
|
||
`jpg`); отвергнуть файл больше `UPLOAD_MAX_BYTES` (50 МиБ) или неподдерживаемого типа; вычислить sha256; создать
|
||
`s3_object` (`bucket_role = uploads`, ключ `uploads/{file_id}/{job_id}/{безопасное имя}`, `sha256`, `size_bytes`,
|
||
`content_type`, `created_by = user:{id}`), положить объект в S3, создать `stored_file` (`status = UPLOADED`,
|
||
`version_no = 1`, `uploaded_by`, `uploaded_at`, `content_hash`, `original_name`, `consolidation_id = NULL`) и
|
||
`recognition_job` (`status = queued`, `pipeline` по типу: xls/xlsx/xlsb → `tabular`, pdf/png/jpg → `image`;
|
||
`max_attempts` 3 / 1), установить `stored_file.current_recognition_job_id`, записать
|
||
`audit_event(FILE_UPLOAD, PRIMARY_DOCUMENTS)` с `after_value.consolidation_id = null`. Ответ SHALL
|
||
перечислять принятые файлы (`accepted[]` с `file_id`, `job_id`, `name`) и отклонённые (`rejected[]` с `name`, `code`,
|
||
`message`); код ответа 201, если принят хотя бы один, иначе 422. Тип документа и период при загрузке MUST NOT
|
||
запрашиваться (макет `40`). Без доступного S3 — 503 `STORAGE_UNAVAILABLE`.
|
||
Query-параметр `consolidation_version_id` MUST NOT приниматься (удалён в v5.18): загрузка этим маршрутом — без
|
||
контекста консолидации и не меняет статусы версий; загрузка в консолидацию — требование «Загрузка файлов в
|
||
консолидацию».
|
||
|
||
#### Scenario: Реальная ОСВ принята и ушла в outbox
|
||
- **WHEN** `ka_rf` загружает `rb/2024-FY/ОСВ_66 за 2024.xls` для ЮЛ своего филиала
|
||
- **THEN** 201, `accepted[0].file_id` существует; `stored_file.status = UPLOADED`, `file_type = XLS`, `content_hash` =
|
||
sha256 файла, `consolidation_id IS NULL`; `recognition_job.status = queued`, `pipeline = tabular`; объект в S3 по ключу
|
||
`uploads/{file_id}/{job_id}/ОСВ_66 за 2024.xls`; публикатор outbox публикует запрос с этим ключом
|
||
|
||
#### Scenario: Смесь принятых и отклонённых
|
||
- **WHEN** в одном запросе xlsx, файл `.docx` и файл 51 МиБ
|
||
- **THEN** 201, `accepted` — один, `rejected` — два с кодами `UNSUPPORTED_FILE` и `FILE_TOO_LARGE`
|
||
|
||
#### Scenario: Расширение не совпадает с содержимым
|
||
- **WHEN** загружается файл `osv.xlsx`, байты которого — BIFF8
|
||
- **THEN** файл отклонён с кодом `UNSUPPORTED_FILE`
|
||
|
||
#### Scenario: Чужое ЮЛ
|
||
- **WHEN** `ka_rf` загружает файл для ЮЛ другого филиала
|
||
- **THEN** 404, объектов и записей нет
|
||
|
||
#### Scenario: Загрузка в контексте версии
|
||
- **WHEN** у ГК ЮЛ есть версия `INITIATED` с этим ЮЛ в периметре, файл загружен маршрутом ЮЛ с query
|
||
`consolidation_version_id` (как делал клиент до v5.18)
|
||
- **THEN** 201, параметр игнорируется; `stored_file.consolidation_id IS NULL`; версия остаётся `INITIATED`
|
||
|
||
#### Scenario: ЮЛ вне периметра
|
||
- **WHEN** ЮЛ не входит в периметр ни одной версии, файл загружен маршрутом ЮЛ
|
||
- **THEN** 201 — маршрут ЮЛ периметр не проверяет; проверка периметра — только у загрузки в консолидацию
|
||
|
||
### Requirement: Дедуп по содержимому
|
||
Файл с тем же sha256, что у существующего `stored_file` того же ЮЛ со статусом не `SUPERSEDED`, SHALL отклоняться с
|
||
кодом `DUPLICATE_FILE` и `file_id` существующего. Тот же файл у другого ЮЛ SHALL приниматься.
|
||
|
||
#### Scenario: Повторная загрузка того же файла
|
||
- **WHEN** тот же xls загружается во второй раз для того же ЮЛ
|
||
- **THEN** `rejected[0].code = DUPLICATE_FILE`, `rejected[0].file_id` — прежний файл; новых записей нет
|
||
|
||
### Requirement: Жизненный цикл статуса файла
|
||
`stored_file.status` SHALL меняться только по заданию `current_recognition_job_id`: `UPLOADED` при загрузке и при принятом ручном повторе с новым текущим заданием;
|
||
`PROCESSING` при ack сервиса; `ERROR` при финальной ошибке задания (`error`, `timeout`, `dlq`) и при невозможности
|
||
маппинга; `PROCESSED` после успешного маппинга; `SUPERSEDED` при автозамене. Карточка файла SHALL показывать причину
|
||
`ERROR` (`error_code`/`error_message` задания или ошибку маппинга).
|
||
|
||
#### Scenario: Парсер вернул ошибку
|
||
- **WHEN** конверт `error UNSUPPORTED_FORMAT` для задания файла
|
||
- **THEN** `stored_file.status = ERROR`, в карточке файла `error_code = UNSUPPORTED_FORMAT`
|
||
|
||
#### Scenario: Сервис не ответил
|
||
- **WHEN** задание переведено вахтёром в `timeout`
|
||
- **THEN** файл — `ERROR` с `error_code = TIMEOUT`
|
||
|
||
### Requirement: Автозамена прежних версий
|
||
После успешного маппинга файла ядро SHALL найти другие файлы того же ЮЛ со статусом не `SUPERSEDED`, у которых есть
|
||
документ с теми же `document_type`, `osv_account_code`, `period_year`, `period_quarter`, и перевести их в
|
||
`SUPERSEDED` (`superseded_by_file_id` = новый файл, `updated_at`); новый файл получает `version_no` = максимум среди
|
||
заменённых + 1. Если у нового файла `consolidation_id IS NULL`, он SHALL получить `consolidation_id` первого по `id`
|
||
заменённого файла, у которого он задан (v5.18); файл, загруженный в консолидацию, сохраняет свой `consolidation_id`.
|
||
Документы и строки заменённых файлов SHALL сохраняться (история), но по умолчанию не показываться.
|
||
Привязки `consolidation_document` документов заменённых файлов к версиям «в работе» SHALL удаляться в той же
|
||
транзакции (версии блокируются `FOR UPDATE` по возрастанию `id` после старых файлов); привязки к версиям в других
|
||
статусах не трогаются; статус версии не меняется. Аудит `FILE_REPLACE` (`object_id` заменённого, `after_value` с
|
||
новым файлом и `unbound_version_ids`, в `description` — перечень заменённых документов и признак `confirmed`, если
|
||
заменён документ `CONFIRMED`). Решение владельца 05.10.2026: замена происходит и для `CONFIRMED`.
|
||
Замена остаётся на уровне файла (OQ-115), но если у заменённого файла есть документ, ключа которого
|
||
(`document_type`, `osv_account_code`, `period_year`, `period_quarter`) нет среди документов нового файла (в т. ч. документ
|
||
без года или квартала), ядро SHALL добавить в `metrics.mapping_warnings` задания нового файла (карточка файла,
|
||
`JobBrief.mapping_warnings`) по одному предупреждению на заменённый файл (OQ-154): имя и версия заменённого файла,
|
||
выпавшие документы (тип, счёт, период, статус — в т. ч. `CONFIRMED`) и версии консолидации «в работе», с которых сняты
|
||
привязки этих документов. Поведение замены и статусы документов нового файла от предупреждения не меняются.
|
||
Пара «новый документ — прежний» с `counterparty_check = MISMATCH` у любого из двух SHALL NOT участвовать в замене
|
||
(AURORA-1644, решение владельца 21.10.2026): документы другой организации, загруженные в карточку ЮЛ, не вытесняют друг
|
||
друга и документы организации карточки. `UNKNOWN` (пустая шапка) несовпадением не считается. Если у ЮЛ есть файл с
|
||
документом того же ключа, оставленный только из-за `MISMATCH`, ядро SHALL добавить в `metrics.mapping_warnings` нового
|
||
файла по одному предупреждению на такой файл: «автозамена не выполнена», имя и версия оставленного файла, его документы
|
||
этого ключа (тип, счёт, период, статус).
|
||
|
||
#### Scenario: Новая ОСВ 60 за 1 кв. 2025
|
||
- **WHEN** у ЮЛ есть `PROCESSED` файл с документом OSV 60 / 2025 / Q1 (`CONFIRMED`), загружен и смапплен новый файл с таким же документом
|
||
- **THEN** прежний файл — `SUPERSEDED`, `superseded_by_file_id` = новый, у нового `version_no = 2`; `audit_event(FILE_REPLACE)`
|
||
с `description`, содержащим `confirmed=true`; список файлов ЮЛ по умолчанию показывает только новый
|
||
|
||
#### Scenario: Заменённый документ был в версии
|
||
- **WHEN** заменённый `CONFIRMED` документ ОСВ 60 за 2024 Q2 был привязан к версии 2024 Q3 в `DATA_CORRECTION`
|
||
- **THEN** привязка удалена; в чек-листе ячейка ОСВ 60 показывает `missing_quarters = ["Q2"]` (или `NEEDS_CHECK`
|
||
по новому документу), пока новый документ не подтверждён; статус версии прежний
|
||
|
||
#### Scenario: Отдельный ОФР заменил книгу «ББ + ОФР»
|
||
- **WHEN** у ЮЛ есть файл с `CONFIRMED` ББ и ОФР за Q3 2025, ББ привязан к версии 2025 Q3 в `DATA_CORRECTION`; смаплен
|
||
новый файл только с ОФР за Q3 2025
|
||
- **THEN** прежний файл — `SUPERSEDED`, привязка ББ снята; в `metrics.mapping_warnings` нового задания одно предупреждение:
|
||
имя прежнего файла, `BALANCE 2025 Q3 (CONFIRMED)` и версия «ГК» 2025 Q3, версия 1; ОФР в предупреждении нет
|
||
|
||
#### Scenario: Новый файл без контекста наследует консолидацию
|
||
- **WHEN** прежний файл загружен в консолидацию 2025 Q1 (`consolidation_id = C`), новый файл с тем же документом загружен
|
||
маршрутом ЮЛ (`consolidation_id IS NULL`) и смаплен
|
||
- **THEN** у нового файла `consolidation_id = C`
|
||
|
||
#### Scenario: Новый файл с контекстом сохраняет свой
|
||
- **WHEN** прежний файл в консолидации 2025 Q1 (`C1`), новый с тем же документом загружен в консолидацию 2025 Q2 (`C2`)
|
||
- **THEN** у нового файла `consolidation_id = C2`
|
||
|
||
#### Scenario: Документы разных организаций в одной карточке
|
||
- **WHEN** в карточку ЮЛ (ИНН 6609493015) последовательно загружены и смаплены три скана ББ за 2026 Q2 разных
|
||
организаций (в шапках другие ИНН, `counterparty_check = MISMATCH`)
|
||
- **THEN** ни один файл не `SUPERSEDED`, список файлов ЮЛ показывает все три, у каждого `version_no = 1`, `FILE_REPLACE`
|
||
нет; у второго и третьего в `metrics.mapping_warnings` — «автозамена не выполнена» с именами и версиями оставленных файлов
|
||
|
||
#### Scenario: Документ организации карточки не вытесняет чужой
|
||
- **WHEN** у ЮЛ есть файл с ББ 2026 Q2 `MISMATCH`, смаплен новый файл с ББ 2026 Q2 `MATCH`
|
||
- **THEN** прежний файл остаётся `PROCESSED`, у нового `version_no = 1` и предупреждение «автозамена не выполнена»
|
||
|
||
#### Scenario: Пустая шапка заменяется как прежде
|
||
- **WHEN** у прежнего или нового документа того же ключа `counterparty_check = UNKNOWN`, у другого — не `MISMATCH`
|
||
- **THEN** прежний файл — `SUPERSEDED`, у нового `version_no = 2`, предупреждения «автозамена не выполнена» нет
|
||
|
||
### Requirement: Удаление файла
|
||
`DELETE /api/v1/files/{file_id}` (`DOCUMENT_EDIT`, policy) SHALL удалить строки ОСВ, документы, задания и `stored_file`,
|
||
проставить `s3_object.expires_at = now()` (объект физически не удаляется, OQ-22) и записать `audit_event(FILE_DELETE,
|
||
before_value = карточка файла, after_value = {unbound_version_ids})`. Удаление MUST быть отклонено 409
|
||
`FILE_HAS_CONFIRMED_DOCUMENTS`, если у файла есть документ `CONFIRMED`, подтверждённый пользователем (`confirmed_by` не
|
||
NULL), или если автоподтверждённый документ файла (`confirmed_by` NULL) привязан к версии в статусе, отличном от
|
||
`INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION` (OQ-157, уточняет OQ-110); и 409 `FILE_SUPERSEDES_OTHERS`, если на
|
||
файл ссылаются `superseded_by_file_id`. При удалении файла с автоподтверждёнными документами их привязки
|
||
`consolidation_document` SHALL сниматься, статус версий MUST NOT меняться, `after_value.unbound_version_ids` — id версий,
|
||
где привязки сняты (по возрастанию; пустой список, если привязок не было). Блокировки: задания → файл → документы →
|
||
advisory-блокировка привязок ГК → версии по `id`. Файл `SUPERSEDED` удалить можно.
|
||
|
||
#### Scenario: Удаление ошибочного файла
|
||
- **WHEN** файл в `ERROR` удаляется владельцем
|
||
- **THEN** 204; `stored_file`, `recognition_job` отсутствуют; `s3_object.expires_at` заполнен; `audit_event(FILE_DELETE)`
|
||
с `after_value.unbound_version_ids = []`
|
||
|
||
#### Scenario: Подтверждённый документ
|
||
- **WHEN** удаляется файл с документом `CONFIRMED`, у которого `confirmed_by` = пользователь
|
||
- **THEN** 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, ничего не изменилось
|
||
|
||
#### Scenario: Автоподтверждённый документ в корректировке
|
||
- **WHEN** версия 2025 Q1 в `DATA_CORRECTION`, к ней привязан автоподтверждённый документ ОСВ 60 РБ; файл удаляется
|
||
- **THEN** 204; привязка снята, версия осталась в `DATA_CORRECTION`; `FILE_DELETE.after_value.unbound_version_ids` = [id версии]
|
||
|
||
#### Scenario: Версия ушла на согласование
|
||
- **WHEN** автоподтверждённый документ файла привязан к версии в `CONSOLIDATION_RF`
|
||
- **THEN** 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, привязка и файл на месте
|
||
|
||
#### Scenario: Автоподтверждённый без привязок
|
||
- **WHEN** удаляется файл, все документы которого автоподтверждены и ни к одной версии не привязаны
|
||
- **THEN** 204; `after_value.unbound_version_ids = []`
|
||
|
||
### Requirement: Загрузка файлов в консолидацию
|
||
`POST /api/v1/consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` (multipart `files`, право
|
||
`DOCUMENT_EDIT`; §9.5 п. 8 v5.18) SHALL до приёма файлов проверить: консолидация видима (ГК в области пользователя;
|
||
чужая и несуществующая — одинаковый 404 `NOT_FOUND` «консолидация не найдена»), ЮЛ видимо (404), у консолидации есть
|
||
хотя бы одна версия со статусом «в работе» (`INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION`; иначе 409
|
||
`CONSOLIDATION_NOT_EDITABLE`), ЮЛ — член периметра хотя бы одной такой версии (иначе 422 `ENTITY_NOT_IN_PERIMETER`
|
||
объектом `detail`). Приём файлов, дедуп, ответ и коды `rejected[]` — как у маршрута ЮЛ; принятый файл SHALL получить
|
||
`stored_file.consolidation_id = consolidation_id`, `FILE_UPLOAD.after_value.consolidation_id` = его строка. Если принят
|
||
хотя бы один файл, ядро SHALL в отдельной транзакции после приёма заблокировать `FOR UPDATE ORDER BY id` версии этой
|
||
консолидации со статусом `INITIATED` и ЮЛ в периметре и перевести каждую в `DOCUMENTS_PROCESSING` (`changed_by` =
|
||
загрузивший, запись `consolidation_status_history` и `CONSOLIDATION_STATUS_CHANGE`); сбой перехода логируется и не
|
||
меняет ответ. Блокировка версии (`consolidation_lock`) для загрузки MUST NOT требоваться.
|
||
|
||
#### Scenario: Загрузка в консолидацию переводит версию
|
||
- **WHEN** у консолидации 2024 Q3 версия 1 в `INITIATED`, ЮЛ в периметре, загружен один принятый файл
|
||
- **THEN** 201; `stored_file.consolidation_id` = консолидация; версия в `DOCUMENTS_PROCESSING`, история с `changed_by`
|
||
= пользователь; `FILE_UPLOAD.after_value.consolidation_id` = id консолидации
|
||
|
||
#### Scenario: Версия в другом рабочем статусе
|
||
- **WHEN** единственная версия консолидации в `DATA_CORRECTION`, загружен принятый файл
|
||
- **THEN** 201; статус версии не меняется; файл получил `consolidation_id`
|
||
|
||
#### Scenario: Нет версии в работе
|
||
- **WHEN** единственная версия консолидации `CANCELLED`
|
||
- **THEN** 409 `CONSOLIDATION_NOT_EDITABLE`, файлы не приняты, объектов в S3 нет
|
||
|
||
#### Scenario: ЮЛ вне периметра
|
||
- **WHEN** ЮЛ той же ГК не входит в периметр ни одной версии «в работе»
|
||
- **THEN** 422 `ENTITY_NOT_IN_PERIMETER`, файлы не приняты
|
||
|
||
#### Scenario: Чужая и несуществующая консолидация
|
||
- **WHEN** `ka_rf` загружает файл в консолидацию ГК другого филиала и в несуществующий id
|
||
- **THEN** оба ответа 404 с одинаковым телом (`code = NOT_FOUND`), записей нет
|
||
|
||
#### Scenario: Все файлы отклонены
|
||
- **WHEN** в консолидацию с версией `INITIATED` загружен только `.docx`
|
||
- **THEN** 422 телом `UploadResult`, версия остаётся `INITIATED`
|
||
|
||
#### Scenario: Загрузка не требует блокировки версии
|
||
- **WHEN** версию держит другой пользователь (`consolidation_lock`), ЮЛ в периметре
|
||
- **THEN** 201; версия `INITIATED` → `DOCUMENTS_PROCESSING`
|
||
|
||
### Requirement: Очистка форм перед выкладкой
|
||
Перед выкладкой ядра с миграцией v5.19 на контур (шаг 3 порядка выкладки) база SHALL очищаться от форм ББ/ОФР (Р-24):
|
||
формы пересоздаются повторной загрузкой файлов уже по новым правилам. Очистку выполняет владелец или платформенная
|
||
команда по его заявке, на контуре — после резервной копии БД и до `alembic upgrade` v5.19 (на стендах разработчиков — по
|
||
желанию). Инструмент — скрипт ядра `scripts/purge_statements.py` (пишется в PR шага 3; описание здесь, кода нет) с
|
||
режимами `--dry-run` (по умолчанию: только отчёт) и `--apply`, одной транзакцией:
|
||
1. **Отбор:** документы `BALANCE` / `INCOME` (у файлов любого статуса, включая `SUPERSEDED`).
|
||
2. **Отказ без изменений**, если хотя бы один отобранный документ упомянут в `ex_snapshot_item` или привязан
|
||
(`consolidation_document`) к версии консолидации в статусе «Верифицирована» или дальше — `VERIFIED` или `ARCHIVED`
|
||
верифицированной версии (есть `ex_snapshot` с `source_version_id` версии): такая версия read-only и заморожена в ЕХ.
|
||
Отчёт называет версии, решение — за владельцем. Версии в работе, на согласовании, отклонённые (`REJECTED`) и
|
||
отменённые (`CANCELLED`, в том числе архивированные) очистке не мешают: привязки к ним снимаются, как к версиям
|
||
«в работе» (п. 3, 4), статус версий не меняется (Р-35).
|
||
3. **Файлы только с формами** — у файла нет документов `OSV`: удаляется файл целиком, как `DELETE /files/{id}` (§9.5 п. 5),
|
||
но без запрета `FILE_HAS_CONFIRMED_DOCUMENTS`: привязки документов к неверифицированным версиям (п. 2) снимаются
|
||
(статус версий не меняется), удаляются документы всех типов файла (`BALANCE`, `INCOME`, `DISCLOSURE`, `OTHER`) с
|
||
`financial_statement_*` и комментариями документов, задания `recognition_job` файла и `stored_file`; объектам S3
|
||
ставится `expires_at = now()`; ссылка `superseded_by_file_id` других файлов на удаляемый обнуляется, а заменённые им
|
||
файлы остаются `SUPERSEDED` (повторная загрузка заменит их снова; отчёт перечисляет такие файлы, если в них есть ОСВ).
|
||
4. **Смешанные файлы** — у файла есть и ОСВ, и формы: удаляются только документы `BALANCE` / `INCOME` с их
|
||
`financial_statement_*`, комментариями и привязками; файл, ОСВ, `DISCLOSURE` / `OTHER` и задания остаются. Повторно
|
||
загрузить такой файл нельзя (дедуп по `sha256`), поэтому отчёт `--dry-run` перечисляет смешанные файлы отдельно, и
|
||
владелец решает до `--apply`, удалять ли их целиком (ключ `--include-mixed`).
|
||
5. **Аудит:** на каждый удалённый файл — системное `FILE_DELETE` (`user_id = NULL`, описание «очистка форм перед v5.19»,
|
||
карточка файла в `before_value`); на документ смешанного файла — то же событие по файлу с перечнем удалённых
|
||
документов.
|
||
6. **Не трогаются:** ОСВ (`osv_entry`), консолидации и их версии, корректировки, гудвилл, справочники, пользователи,
|
||
журнал аудита. Результаты КФО неверифицированных версий, посчитанные с удалёнными формами, пересчитываются, как после
|
||
удаления файла.
|
||
|
||
#### Scenario: Отчёт перед очисткой
|
||
- **WHEN** владелец запускает скрипт без ключей
|
||
- **THEN** скрипт ничего не меняет и печатает число файлов только с формами, смешанных файлов, документов форм,
|
||
подтверждённых документов и привязок к неверифицированным версиям
|
||
|
||
#### Scenario: Форма в верифицированной версии
|
||
- **WHEN** документ ББ привязан к версии `VERIFIED`
|
||
- **THEN** `--apply` отказывает с перечнем версий, база не изменена
|
||
|
||
#### Scenario: Форма в версии на согласовании или отклонённой
|
||
- **WHEN** документ ББ привязан к версии `VERIFICATION_RF`, а ОФР — к версии `REJECTED`; верифицированных версий нет
|
||
- **THEN** `--apply` выполняется: привязки сняты, статусы версий не изменились
|
||
|
||
#### Scenario: Очистка и повторная загрузка
|
||
- **WHEN** очистка выполнена, ядро v5.19 выложено, аналитик загружает тот же PDF с ББ и ОФР
|
||
- **THEN** дедуп не мешает (файла больше нет), формы создаются по мапперу 1.x или 2.0 — смотря что шлёт сервис
|
||
|
||
### Requirement: Ручной повтор обработки файла
|
||
`POST /api/v1/files/{file_id}/retry` SHALL требовать DOCUMENT_EDIT и видимость ЮЛ. Повтор разрешён для
|
||
текущего задания tabular в timeout/TIMEOUT или dlq/NO_ACK при attempt < max_attempts, отсутствии документов
|
||
и файле ERROR. Успех SHALL вернуть 202 с карточкой файла и новым текущим заданием. Несуществующий или чужой файл — 404.
|
||
|
||
#### Scenario: После таймаута
|
||
- **WHEN** табличный файл ОСВ РБ завершился timeout/TIMEOUT, attempt = 1, max_attempts = 3
|
||
- **THEN** 202, создано новое задание attempt = 2, прежнее осталось, в карточке новое текущее задание
|
||
|
||
#### Scenario: Неподходящее состояние
|
||
- **WHEN** повтор запрошен для OCR, done, needs_check, error сервиса, ошибки маппинга либо файла с документами
|
||
- **THEN** 409 FILE_NOT_RETRYABLE без нового задания и аудита
|
||
|
||
#### Scenario: Лимит исчерпан
|
||
- **WHEN** подходящее задание имеет attempt = max_attempts
|
||
- **THEN** 409 MAX_ATTEMPTS_REACHED, состояние не меняется
|
||
|
||
### Requirement: Пакет повторной обработки версии
|
||
`POST /api/v1/consolidations/versions/{version_id}/files/retry` SHALL выбирать видимые ERROR-файлы
|
||
консолидации по периметру версии. Другие статусы не входят в пакет и не блокируются.
|
||
Ответ — 202 с accepted[{file_id, job_id, attempt}] и skipped[{file_id, code}]. В skipped SHALL входить
|
||
только ERROR-файлы, которые нельзя повторить (FILE_NOT_RETRYABLE, MAX_ATTEMPTS_REACHED).
|
||
Файлы без контекста консолидации MUST NOT включаться по совпадению ЮЛ.
|
||
|
||
#### Scenario: Смешанный пакет
|
||
- **WHEN** у версии есть файл timeout/TIMEOUT, OCR, успешный файл и файл с исчерпанным лимитом
|
||
- **THEN** новый job создаётся только для первого; OCR-файл в skipped с `FILE_NOT_RETRYABLE`, файл с исчерпанным
|
||
лимитом — с `MAX_ATTEMPTS_REACHED`; успешный (`PROCESSED`) файл не попадает ни в accepted, ни в skipped
|
||
|
||
#### Scenario: Периметр и контекст
|
||
- **WHEN** файл загружен в другую консолидацию либо его ЮЛ не входит в выбранную версию
|
||
- **THEN** файл не участвует ни в accepted, ни в skipped этой версии
|
||
|
||
#### Scenario: Общие файлы разных версий
|
||
- **WHEN** две версии одной консолидации содержат одно ЮЛ и вызывают пакетный повтор
|
||
- **THEN** они используют общий набор файлов консолидации; уже повторяемый файл второй раз не запускается
|
||
|
||
#### Scenario: Пустой пакет
|
||
- **WHEN** подходящих файлов нет
|
||
- **THEN** 202 с пустым accepted; в skipped — только файлы в `ERROR`, которые повторить нельзя (без них список пуст);
|
||
аудита повторов нет
|
||
|
||
### Requirement: Ограничения версии при повторе
|
||
Повтор в контексте версии SHALL разрешаться только для версий «в работе» (INITIATED, DOCUMENTS_PROCESSING,
|
||
DATA_CORRECTION). Иначе SHALL возвращаться 409 VERSION_NOT_EDITABLE. consolidation_lock MUST NOT требоваться.
|
||
Одиночный повтор файла с consolidation_id SHALL требовать наличия версии «в работе» с ЮЛ файла в периметре;
|
||
без consolidation_id SHALL применяться только policy ЮЛ. Повтор MUST NOT изменять статусы версий.
|
||
|
||
#### Scenario: Проверенная версия
|
||
- **WHEN** пакет запрошен для VERIFIED
|
||
- **THEN** 409 VERSION_NOT_EDITABLE, ни один файл не изменён
|
||
|
||
#### Scenario: Консолидация уже закрыта для обработки
|
||
- **WHEN** одиночный повтор файла консолидации запрошен без версии «в работе» с его ЮЛ
|
||
- **THEN** 409 VERSION_NOT_EDITABLE; другая версия консолидации не открывает недоступный пакет версии
|
||
|
||
#### Scenario: Сохранение статуса
|
||
- **WHEN** пакет допустимой версии INITIATED принят без пользовательской блокировки
|
||
- **THEN** попытки созданы, INITIATED сохраняется; дальнейшие переходы происходят по существующим событиям
|