docs(openspec): change auto-confirm-clean-documents — спека автоподтверждения чистых документов (#64)

Proposal, design (D-1…D-6, решения без обсуждения №1–7) и дельты osv-result-mapping,
primary-files, audit-trail: чистый документ при маппинге сразу CONFIRMED (система,
confirmed_by NULL; переход версии — от загрузившего), удаление автоподтверждённых
файлов до DATA_CORRECTION включительно. Решение владельца 08.10.2026, ТЗ 03_9 п. 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-08 16:36:48 +04:00
parent d8bf5b22dc
commit 3623358fb4
6 changed files with 372 additions and 0 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-08

View File

@ -0,0 +1,111 @@
# Design: автоподтверждение чистых документов
## Context
Маппинг результата (`services/result_mapping.py`, `_map_candidate`) в одной транзакции: `map_file` создаёт документы
файла со статусом `RECOGNIZED` или `NEEDS_CHECK` (ОСВ — `domain/osv_mapping.document_status`, §9.3; формы —
`domain/statement_mapping`, §9.5 D-6; `DISCLOSURE`/`OTHER` — всегда `RECOGNIZED`), автозамена снимает привязки
документов прежних файлов (`on_documents_superseded`), затем хук `on_documents_mapped` переводит версии в `DATA_CORRECTION`
по документам `NEEDS_CHECK` (`changed_by = file.uploaded_by`, design №2 Среза 4). Привязки к версиям создаёт только
ручное подтверждение (`DocumentService.confirm` → `on_document_confirmed`) и подхват при создании версии и правке
периметра (`bind_confirmed`). Удаление файла (`PrimaryFilesService.delete`) запрещено при любом `CONFIRMED` документе.
Порядок блокировок (`CLAUDE.md` arm-core): задание → файл → документы → advisory ГК → версии по `id`. Маппинг уже
держит задание, файл и документы, когда вызывает хуки версий.
## Goals / Non-Goals
**Goals:** чистый документ сразу `CONFIRMED` и в своде без действий аналитика; поведение привязки и статусов версии —
то же, что при ручном подтверждении; ошибочно загруженный чистый файл можно удалить, пока версия не ушла дальше
корректировки.
**Non-Goals:** см. proposal — новые поля, UI, миграция данных, пересмотр `NEEDS_CHECK`.
## Decisions
### D-1. Где: шаг маппинга после автозамены
`_map_candidate`: `map_file` → автозамена (как сейчас) → **`auto_confirm(session, file, documents, now)`** →
`on_documents_mapped`. После автозамены — чтобы привязки прежних версий файла были уже сняты, а замена по ключу (R-1)
работала как при ручном подтверждении. До `on_documents_mapped` — тот смотрит только `NEEDS_CHECK`, автоподтверждённые
его не касаются. Всё в одной транзакции маппинга: при ошибке откатывается и маппинг, и подтверждение (повтор задания —
по счётчику попыток, как сейчас).
### D-2. Кто: система за документ, загрузивший — за переход версии (решение владельца 08.10.2026)
`document.confirmed_by = NULL`, `confirmed_at = now`; у формы `financial_statement_meta.reviewed_by = NULL`,
`reviewed_at = now`. `audit_event(DOCUMENT_CONFIRM, PRIMARY_DOCUMENTS, object_type = document)`: `user_id = NULL`,
`before_value.status = RECOGNIZED`, `after_value.status = CONFIRMED`, `description = «автоподтверждение: без замечаний»`
(DBML: `audit_event.user_id` — «NULL для системных событий»). Переход версии — через `consolidation_status.transition`
с `changed_by = stored_file.uploaded_by` (`consolidation_status_history.changed_by` NOT NULL в DBML) и `comment =
«автоподтверждение документа <тип> <счёт>»`. `on_document_confirmed` получает `changed_by: uuid | None` и `comment`
от вызывающего: ручное подтверждение передаёт пользователя и `None`, автоподтверждение — `file.uploaded_by` и
комментарий; при `changed_by = None` привязка создаётся, переход пропускается с записью в лог.
Признак автоподтверждения — `CONFIRMED` и `confirmed_by IS NULL`: ручное подтверждение всегда пишет пользователя
(`DOCUMENT_EDIT` на маршруте).
### D-3. Что: документ, ставший `RECOGNIZED` при маппинге, и только он
Кандидаты — документы файла со статусом `RECOGNIZED` сразу после `map_file`. Для `BALANCE`/`INCOME` с шапкой
дополнительно — условия ручного подтверждения (D-9 Среза 3b): `period_year` и `period_quarter` определены, жёстких
нарушений §7 нет (на момент маппинга акцептов нет). Условия выносятся из `DocumentService._check_statement` в общую
функцию (одна точка правды; ручной путь продолжает отвечать 409 `PERIOD_REQUIRED`/`CHECKS_NOT_PASSED`, автоматический —
просто оставляет `RECOGNIZED`). По §9.5 D-6 `RECOGNIZED` формы уже требует период и пройденные проверки, так что
расхождение на практике — только защита от рассинхронизации правил. `DISCLOSURE`/`OTHER` (`RECOGNIZED` по D-3 Среза 3b)
подтверждаются тоже: проверять в них нечего, в версии они не привязываются (`fits` — ОСВ и формы).
Пересчёт статуса формы после правки или акцепта (`RECOGNIZED`/`NEEDS_CHECK`) автоподтверждения не запускает: после
исправлений аналитик нажимает «Подтвердить данные» (ТЗ 03_9, 10_10).
### D-4. Удаление: только автоподтверждённое и только до согласования
`PrimaryFilesService.delete` под существующими блокировками (задания → файл → документы) проверяет: если у файла есть
`CONFIRMED` документ с `confirmed_by IS NOT NULL` — 409 `FILE_HAS_CONFIRMED_DOCUMENTS` (как сейчас). Если все
`CONFIRMED` документы автоподтверждены — дальше блокируются версии ЮЛ (`lock_versions_for_entity` по всем статусам, где
есть привязки этих документов; порядок «… → документы → advisory ГК → версии» соблюдён) и проверяется, что каждая
версия с привязкой в `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION`; иначе 409 `FILE_HAS_CONFIRMED_DOCUMENTS` с
текстом «документ уже участвует в версии на согласовании». Затем привязки снимаются (`consolidation_document`), файл
удаляется как сейчас; `FILE_DELETE.after_value = {unbound_version_ids: [...]}` (по возрастанию; пусто — пустой список),
`before_value` — карточка файла, как сейчас. Статус версии не меняется (свод — чистая функция, пересчитается).
`FILE_SUPERSEDES_OTHERS` — без изменений.
### D-5. Блокировки
`auto_confirm` вызывается под заданием, файлом и документами маппинга; версии берёт `on_document_confirmed` через
`lock_versions_for_entity` (advisory ГК → версии `FOR UPDATE` по `id`) — тот же путь, что у ручного подтверждения.
Несколько документов файла — повторные вызовы в одной транзакции по одному ЮЛ: блокировки уже взяты, порядок не
нарушается. Документы файла уже под `FOR NO KEY UPDATE` маппинга (R-3 совместимость со вставкой привязки).
### D-6. Данные и совместимость
Миграции схемы и данных нет. Старые `RECOGNIZED` остаются и подтверждаются кнопкой. API не меняется:
`DocumentOut.confirmed_by` уже `nullable`; `POST …/confirm` для автоподтверждённого отвечает 409 `ALREADY_CONFIRMED`.
Чек-лист: ячейка с автоподтверждённым документом — `CONFIRMED` («Загружен»).
## Risks / Trade-offs
- **Неверный, но «чистый» файл сразу в своде** (например, ОСВ не того ЮЛ с совпавшим ИНН шапки). Раньше его ловил
взгляд аналитика при подтверждении. Смягчение: удаление разрешено до согласования (D-4), автозамена новой версией
файла работает как раньше; документ виден в панели и чек-листе как «Загружен».
- **Нагрузка на транзакцию маппинга** растёт на блокировку версий ЮЛ для каждого документа — то же, что делает хук
`on_documents_mapped`; в комплекте десятки документов, не тысячи.
- **Журнал**: событие `DOCUMENT_CONFIRM` без пользователя — системное, как `FILE_REPLACE`; фильтр журнала по
пользователю его не покажет.
## Migration Plan
Выкатка обычная: миграций нет. После обновления ядра новые результаты подтверждаются автоматически; уже загруженные
`RECOGNIZED` — вручную (или перезагрузкой файла).
## Open Questions
- OQ-157 (новый, docs): автоподтверждение чистых документов — решено владельцем 08.10.2026; пересматривает OQ-05
(`RECOGNIZED` остаётся статусом модели, но после маппинга на нём документ не задерживается) и уточняет OQ-110
(удаление при консолидациях — для автоподтверждённых до `DATA_CORRECTION` включительно).
## Decisions made without discussion
| № | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 2026-10-08 | Автоподтверждение — после автозамены и до `on_documents_mapped` в той же транзакции маппинга | замена привязки по ключу работает как при ручном подтверждении; откат целиком при ошибке | неверный порядок — лишняя/потерянная привязка, ловится тестом автозамены |
| 2 | 2026-10-08 | `DISCLOSURE`/`OTHER` тоже подтверждаются автоматически | в них нечего проверять; ТЗ 03_9 п. 4 не различает типы | расшифровку нельзя удалить без условий D-4 — те же, что у ОСВ |
| 3 | 2026-10-08 | Условия формы — общая функция с ручным подтверждением; при невыполнении документ остаётся `RECOGNIZED` без ошибки | одна точка правды; автоматический путь не должен падать | форма без периода ждёт кнопку, как раньше |
| 4 | 2026-10-08 | После правки/акцепта формы автоподтверждения нет | ТЗ: «Подтвердить данные» после исправлений | лишний клик после правки — осознанный |
| 5 | 2026-10-08 | Существующие `RECOGNIZED` не мигрируются | контур без продовых данных; миграция данных с аудитом без пользователя — лишний риск | на стенде старые документы подтверждаются кнопкой |
| 6 | 2026-10-08 | Комментарий перехода версии — «автоподтверждение документа <тип> <счёт>», описание аудита — «автоподтверждение: без замечаний» | история версии и журнал объясняют, откуда переход без клика | тексты меняются без миграции |
| 7 | 2026-10-08 | Удаление: проверка статусов версий только тех, к которым привязаны документы файла; `FILE_DELETE.after_value.unbound_version_ids` | симметрично `FILE_REPLACE.unbound_version_ids` | — |

View File

@ -0,0 +1,55 @@
# Proposal: автоподтверждение чистых документов
**Срез:** доработка Срезов 2c, 3b и 4 (`../docs/context/09-delivery-plan.md`) — путь «загрузка → свод» без пустых кликов.
**Репозиторий:** arm-core. **Issue:** RSHB/arm-core#64. Правило и OQ — bounded-PR в `../docs` (`05-domain-rules.md` §9.3,
§9.5; `07-open-questions.md` OQ-157, пометка к OQ-05 и OQ-110; гайды `dev/qa-guide.md`, `dev/frontend-api-guide.md`).
DBML и контракты обмена не меняются.
## Why
Документ без замечаний сейчас получает `RECOGNIZED` и попадает в консолидацию только после ручного «Подтвердить»
(`CONFIRMED`, §3 п. 1). Для ОСВ этот клик ничего не проверяет: строки ОСВ не правятся (OQ-114), ошибка исправляется
перезагрузкой файла. На комплекте РБ + Счастливочная за 1 кв. 2025 это 16 нажатий подряд (прогон владельца 08.10.2026).
ТЗ этого шага не требует: 03_9, п. 4 загрузки — «при отсутствии замечаний документ получает статус „Загружен“, при
замечаниях — „Требуется проверка“»; 10_10 — «Загружен» при прохождении всех проверок; «Подтвердить данные» — шаг после
исправлений. Решение владельца 08.10.2026: чистый документ сразу «Загружен».
## What Changes
- **Автоподтверждение при маппинге.** Документ, который маппинг сделал бы `RECOGNIZED`, сразу становится `CONFIRMED`:
`confirmed_by = NULL`, `confirmed_at = now`, у формы ББ/ОФР `reviewed_by = NULL`, `reviewed_at = now`;
`audit_event(DOCUMENT_CONFIRM)` с `user_id = NULL` и `description = «автоподтверждение: без замечаний»`. Форма
подтверждается автоматически только при условиях ручного подтверждения (период определён, жёсткие проверки пройдены),
иначе остаётся `RECOGNIZED`. `DISCLOSURE`/`OTHER` — тоже сразу `CONFIRMED`.
- **Привязка и статус версии — как при ручном подтверждении.** Документ привязывается ко всем подходящим версиям «в
работе» с заменой по ключу (R-1); версия из `INITIATED`/`DOCUMENTS_PROCESSING` переходит в `DATA_CORRECTION` с
`changed_by = stored_file.uploaded_by` и комментарием «автоподтверждение документа …». Файл без `uploaded_by` —
привязка есть, перехода нет, запись в лог (как у хука `on_documents_mapped`, design №2 Среза 4).
- **`NEEDS_CHECK` и правки — вручную, как раньше.** Форма, ставшая чистой после правки или акцепта, подтверждается
кнопкой (ТЗ 03_9: «Подтвердить данные» после исправлений). `RECOGNIZED` остаётся в модели: старые документы и формы
после правок.
- **Удаление файла с автоподтверждёнными документами.** `DELETE /files/{id}` разрешён, если все `CONFIRMED` документы
файла подтверждены автоматически (`confirmed_by IS NULL`) и каждая версия, к которой они привязаны, в `INITIATED`,
`DOCUMENTS_PROCESSING` или `DATA_CORRECTION`; привязки снимаются, статус версии не меняется,
`FILE_DELETE.after_value.unbound_version_ids`. Иначе — 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, как сейчас.
Таблицы DBML v5.17: `document` (`status`, `confirmed_by`, `confirmed_at`), `financial_statement_meta` (`reviewed_by`,
`reviewed_at`), `consolidation_document`, `consolidation_version`, `consolidation_status_history`, `audit_event`,
`stored_file` (`uploaded_by`). Новых полей нет.
## Non-goals
- Признак «подтверждено автоматически» отдельным полем или в API сверх `confirmed_by = null` — не вводится.
- Подсказка в UI arm-web «подтверждено системой», кнопка удаления файла в UI — вне объёма (arm-web).
- Миграция данных: существующие `RECOGNIZED` документы не подтверждаются задним числом.
- Повторный автозапуск после правки формы, межформенная проверка как условие — нет (мягкая, §7).
- Изменение правил `NEEDS_CHECK` (OQ-116, предупреждения маппинга) — нет.
## Impact
- Код: `services/result_mapping.py` (`_map_candidate`), `services/consolidation_documents.py`
(`on_document_confirmed` с внешним `changed_by`/`comment`, новый `auto_confirm`), `services/documents.py` (общая
проверка условий формы), `services/primary_files.py` (`delete`), `repositories/files.py` (снятие привязок).
- Спеки: `osv-result-mapping`, `documents-api`, `primary-files`, `audit-trail`.
- Поведение для фронта: чистые документы приходят сразу `CONFIRMED` («Загружен»); `confirmed_by = null` — признак
автоподтверждения.

View File

@ -0,0 +1,28 @@
# Spec Delta: audit-trail
## MODIFIED Requirements
### Requirement: События файлов и документов
Загрузка, автозамена, удаление файла и подтверждение документа SHALL писать `audit_event` через сервис аудита:
| Действие | `action` | `section` | `object_type` | `user_id` |
|---|---|---|---|---|
| файл принят | `FILE_UPLOAD` | `PRIMARY_DOCUMENTS` | `stored_file` | инициатор |
| прежний файл заменён новым (автозамена после маппинга) | `FILE_REPLACE` | `PRIMARY_DOCUMENTS` | `stored_file` (заменённый) | NULL (система; инициатор загрузки — в `description`) |
| файл удалён | `FILE_DELETE` | `PRIMARY_DOCUMENTS` | `stored_file` | инициатор |
| документ подтверждён пользователем | `DOCUMENT_CONFIRM` | `PRIMARY_DOCUMENTS` | `document` | инициатор |
| документ подтверждён автоматически при маппинге (чистый) | `DOCUMENT_CONFIRM` | `PRIMARY_DOCUMENTS` | `document` | NULL (система; `description` = «автоподтверждение: без замечаний») |
`FILE_REPLACE` SHALL содержать в `after_value` новый `file_id`, в `description` — перечень заменённых документов и
`confirmed=true`, если среди них был `CONFIRMED`. `FILE_DELETE` SHALL содержать в `after_value.unbound_version_ids`
версии, где сняты привязки удалённых автоподтверждённых документов. Смена статусов задания и файла в обмене и в цикле
маппинга `audit_event` не пишет (`ASSUMPTION(OQ-55)`).
#### Scenario: Загрузка двух файлов
- **WHEN** принято два файла одним запросом
- **THEN** два события `FILE_UPLOAD` с `object_id` каждого файла и `after_value` (`original_name`, `content_hash`, `job_id`)
#### Scenario: Автоподтверждение
- **WHEN** смаплен чистый результат ОСВ
- **THEN** одно событие `DOCUMENT_CONFIRM` с `object_id` документа, `user_id` NULL, `before_value.status = RECOGNIZED`,
`after_value.status = CONFIRMED`, `description = «автоподтверждение: без замечаний»`

View File

@ -0,0 +1,140 @@
# Spec Delta: osv-result-mapping
## ADDED Requirements
### Requirement: Автоподтверждение чистого документа
В транзакции маппинга после автозамены прежних версий файла и до перехода версий по `NEEDS_CHECK` цикл маппинга SHALL
перевести каждый документ файла, получивший при маппинге статус `RECOGNIZED`, в `CONFIRMED` (ТЗ 03_9 п. 4, 10_10;
решение владельца 08.10.2026, OQ-157): `confirmed_by = NULL`, `confirmed_at = now`; у формы `BALANCE`/`INCOME` —
`financial_statement_meta.reviewed_by = NULL`, `reviewed_at = now`; `audit_event(DOCUMENT_CONFIRM, PRIMARY_DOCUMENTS,
object_type = document)` с `user_id = NULL`, `before_value.status = RECOGNIZED`, `after_value.status = CONFIRMED` и
`description = «автоподтверждение: без замечаний»`. Форма `BALANCE`/`INCOME` с шапкой SHALL подтверждаться
автоматически, только если выполнены условия ручного подтверждения (`period_year` и `period_quarter` определены, нет
непройденных неакцептованных жёстких проверок §7); иначе она MUST оставаться `RECOGNIZED` без ошибки. Документы
`NEEDS_CHECK` MUST NOT подтверждаться автоматически.
Автоподтверждённый документ SHALL привязываться к версиям так же, как при ручном подтверждении (`documents-api`
«Подтверждение документа»: все подходящие версии «в работе», замена привязки того же ключа R-1), и каждая версия,
получившая привязку, из `INITIATED` или `DOCUMENTS_PROCESSING` SHALL переходить в `DATA_CORRECTION` с `changed_by =
stored_file.uploaded_by` и `comment = «автоподтверждение документа <document_type> <osv_account_code>»` (счёт — только у
ОСВ). Если `uploaded_by` NULL, привязка SHALL создаваться, а переход — пропускаться с записью в лог. Пересчёт статуса
формы после правки или акцепта MUST NOT подтверждать документ автоматически.
#### Scenario: Чистая ОСВ сразу «Загружен»
- **WHEN** смаплен результат `fixtures/osv/rb/2025-Q1/ОСВ сч 60 за 1 кв 2025 год.xlsx` для ЮЛ АО «Русский бисквит»
(задание `done`, шапка совпадает с владельцем, нарушений и предупреждений нет)
- **THEN** документ `OSV` 60 — `CONFIRMED`, `confirmed_by` NULL, `confirmed_at` заполнен; есть `audit_event(DOCUMENT_CONFIRM)`
с `user_id` NULL и описанием «автоподтверждение: без замечаний»
#### Scenario: Привязка и переход версии
- **WHEN** версия ГК «Русский Бисквит» 2025 Q1 в `INITIATED`, РБ в периметре; смаплен чистый результат ОСВ 60 РБ за 2025 Q1,
файл загрузил `ka_go`
- **THEN** `consolidation_document(version, document)` создан; версия в `DATA_CORRECTION`; запись истории
`INITIATED → DATA_CORRECTION` с `changed_by` = `ka_go` и комментарием «автоподтверждение документа OSV 60»
#### Scenario: Документ с замечанием ждёт аналитика
- **WHEN** файл ОСВ загружен для ЮЛ «Пекарня Счастливочная», а в шапке `АО "Русский бисквит"`
- **THEN** документ `NEEDS_CHECK`, `confirmed_at` пуст, события `DOCUMENT_CONFIRM` нет, привязки нет
#### Scenario: Чистая форма Excel
- **WHEN** смаплен `statements-xlsx/mezheninovskaya/2025-Q3/1_Форма2_бух_09` (ОФР, `done`, соотношения сходятся, период определён)
- **THEN** документ `INCOME` `CONFIRMED`, `reviewed_by` NULL, `reviewed_at` заполнен
#### Scenario: Форма OCR не подтверждается сама
- **WHEN** смаплен эталон `ocr/expected_normalize/БО 1 кв 2025` (уверенность значений < 100, дата отчёта не разобрана)
- **THEN** документы `BALANCE` и `INCOME` — `NEEDS_CHECK`, не `CONFIRMED`
#### Scenario: Файл без загрузившего
- **WHEN** версия в `INITIATED`, ЮЛ в периметре; смаплен чистый результат файла с `uploaded_by` NULL
- **THEN** документ `CONFIRMED` и привязан к версии; версия осталась `INITIATED`; в логе — строка о пропуске перехода
#### Scenario: Автозамена чистой версией файла
- **WHEN** документ ОСВ 60 РБ за 2025 Q1 из файла A привязан к версии 2025 Q1; загружен и смаплен файл B того же ключа
(чистый), A заменён автозаменой
- **THEN** привязка документа A снята, документ B `CONFIRMED` и привязан к версии
## MODIFIED Requirements
### Requirement: Проверки шапки и статус документа
`counterparty_check` SHALL быть `MATCH`, если ИНН шапки равен ИНН владельца, или (без ИНН) нормализованное имя шапки
совпадает с нормализованным именем или алиасом владельца; `MISMATCH` иначе; `UNKNOWN` при пустой шапке.
`period_check` SHALL быть `UNKNOWN` до появления версии консолидации (Срез 4). Для `period_quarter ∈ {Q2, Q3, Q4}` с
`is_cumulative = false` ядро SHALL сверить сальдо на начало корневой строки с документом `Q1` того же года, ЮЛ и
счёта (файл не `SUPERSEDED`) и при совпадении (±0.01) установить `is_cumulative = true` с предупреждением
(`ASSUMPTION(OQ-02)`). Статус документа при маппинге SHALL быть `RECOGNIZED`, если задание `done`, `checks` без
нарушений, `counterparty_check ≠ MISMATCH` и нет предупреждений маппинга; иначе `NEEDS_CHECK` (OQ-05). Документ
`RECOGNIZED` в той же транзакции SHALL подтверждаться автоматически (требование «Автоподтверждение чистого документа»).
#### Scenario: Шапка другой организации
- **WHEN** файл загружен для ЮЛ «Пекарня Счастливочная», а в шапке `АО "Русский бисквит"`
- **THEN** `counterparty_check = MISMATCH`, `status = NEEDS_CHECK`
#### Scenario: Нарастающая по данным
- **WHEN** у ЮЛ есть документ ОСВ 60 за 2024 Q1 (`opening_*` корня = X) и маппится ОСВ 60 «за 3 квартал 2024» с `opening_*` корня = X
- **THEN** `is_cumulative = true`, предупреждение «нарастающая по данным Q1»
#### Scenario: Чистый результат
- **WHEN** задание `done`, шапка совпадает с владельцем, нарушений нет
- **THEN** после маппинга `status = CONFIRMED` (автоподтверждение), `counterparty_check = MATCH`, `period_check = UNKNOWN`
### Requirement: Документ NEEDS_CHECK переводит версию в корректировку
После фиксации документов файла и автоподтверждения чистых документов цикл маппинга SHALL для каждой версии в
`DOCUMENTS_PROCESSING`, в периметр которой входит ЮЛ файла, проверить документы файла: если среди них есть документ
`NEEDS_CHECK`, подходящий версии по периоду (правила привязки `documents-api`), версия SHALL перейти в `DATA_CORRECTION`
с `changed_by = stored_file.uploaded_by` в той же транзакции маппинга (версии блокируются `FOR UPDATE` по `id` после
файла). Если `uploaded_by` NULL, переход SHALL пропускаться с записью в лог. Привязка `consolidation_document` для
документа `NEEDS_CHECK` при маппинге MUST NOT создаваться (привязку при маппинге создаёт только автоподтверждение).
#### Scenario: Предупреждение маппинга двигает статус
- **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл ЮЛ периметра с ОСВ 62 за 2024 Q3 в `NEEDS_CHECK`
- **THEN** версия в `DATA_CORRECTION`, запись истории с `changed_by` = загрузивший; `consolidation_document` нет
#### Scenario: Документ RECOGNIZED
- **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл с единственным чистым документом за 2024 Q3
- **THEN** документ `CONFIRMED` и привязан; переход версии в `DATA_CORRECTION` записан автоподтверждением (комментарий
«автоподтверждение документа …»), этот хук второго перехода не делает
#### Scenario: Документ не подходит версии
- **WHEN** версия 2024 Q3 в `DOCUMENTS_PROCESSING`; смапплен файл с единственным документом `NEEDS_CHECK` за 2025 Q1
- **THEN** статус версии не изменился
### Requirement: Статус документа формы
Документ `BALANCE`/`INCOME` SHALL быть `RECOGNIZED`, только если одновременно: задание `done`; нет непройденных
неакцептованных жёстких проверок §7 (`statement-checks`); `counterparty_check ≠ MISMATCH`; `period_year` и
`period_quarter` определены; у каждого значения `is_modified = true` либо (`is_recognized` не `false` и `confidence`
равна 100 или `NULL`); нет предупреждений маппинга по документу; есть хотя бы одна строка. Иначе `NEEDS_CHECK`. Статус
SHALL пересчитываться при маппинге, после правки формы и после акцепта, кроме документа `CONFIRMED`. Документ
`RECOGNIZED` после маппинга SHALL подтверждаться автоматически (требование «Автоподтверждение чистого документа»);
`RECOGNIZED`, полученный пересчётом после правки или акцепта, — только вручную. Документ формы `NEEDS_CHECK` SHALL
участвовать в переходе версии в `DATA_CORRECTION` наравне с ОСВ (требование «Документ NEEDS_CHECK переводит версию в
корректировку»).
#### Scenario: Чистая форма Excel
- **WHEN** смаплен `mezheninovskaya/2025-Q3/1_Форма2_бух_09` (ОФР, `done`, соотношения сходятся, ИНН шапки = ИНН владельца файла)
- **THEN** документ `INCOME` `CONFIRMED` (автоподтверждение)
#### Scenario: Форма OCR
- **WHEN** смаплен эталон `ocr/expected_normalize/aep_3` (уверенность значений < 100, ИНН шапки ≠ ИНН владельца)
- **THEN** документ `BALANCE` `NEEDS_CHECK`, `counterparty_check = MISMATCH`
#### Scenario: Форма двигает версию
- **WHEN** версия ГК 2025 Q3 в `DOCUMENTS_PROCESSING`, в периметре ЮЛ файла; смаплена форма `BALANCE` за 2025 Q3 в `NEEDS_CHECK`
- **THEN** версия в `DATA_CORRECTION`, привязки `consolidation_document` нет
#### Scenario: Чистая после правки — вручную
- **WHEN** форма `NEEDS_CHECK` исправлена аналитиком (`PATCH …/statement`) и пересчёт дал `RECOGNIZED`
- **THEN** документ остаётся `RECOGNIZED` до `POST …/confirm`
### Requirement: Повтор формы в файле и прочие документы
Второй документ `BALANCE` или `INCOME` в одном файле MUST NOT создаваться: предупреждение маппинга, первый документ —
`NEEDS_CHECK`. Документы `DISCLOSURE` и `OTHER` результата SHALL давать по одному `document` каждого типа на файл без
данных (`locator` — все `locator` этого типа через запятую, статус при маппинге `RECOGNIZED`, затем автоподтверждение →
`CONFIRMED`). Форма `BALANCE`/`INCOME` без строк SHALL создаваться с шапкой и колонками (если колонки есть в результате).
#### Scenario: Расшифровки
- **WHEN** смаплен синтетический снимок `st06_header_variants` (форма `INCOME` и пять `DISCLOSURE`)
- **THEN** у файла два документа: `INCOME` и один `DISCLOSURE` с пятью `locator` через запятую; `DISCLOSURE` — `CONFIRMED`
#### Scenario: Форма без колонки кодов
- **WHEN** смаплен синтетический снимок `st03_no_codes` (`BALANCE`, строк 0)
- **THEN** документ `BALANCE` `NEEDS_CHECK` с шапкой, строк нет (колонок в снимке тоже нет — создаются те, что есть в результате)

View File

@ -0,0 +1,36 @@
# Spec Delta: primary-files
## MODIFIED Requirements
### 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 = []`