diff --git a/openspec/changes/auto-confirm-clean-documents/.openspec.yaml b/openspec/changes/auto-confirm-clean-documents/.openspec.yaml new file mode 100644 index 0000000..01660ba --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-08 diff --git a/openspec/changes/auto-confirm-clean-documents/design.md b/openspec/changes/auto-confirm-clean-documents/design.md new file mode 100644 index 0000000..6621e0d --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/design.md @@ -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` | — | diff --git a/openspec/changes/auto-confirm-clean-documents/proposal.md b/openspec/changes/auto-confirm-clean-documents/proposal.md new file mode 100644 index 0000000..919db8d --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/proposal.md @@ -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` — признак + автоподтверждения. diff --git a/openspec/changes/auto-confirm-clean-documents/specs/audit-trail/spec.md b/openspec/changes/auto-confirm-clean-documents/specs/audit-trail/spec.md new file mode 100644 index 0000000..3a442a4 --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/specs/audit-trail/spec.md @@ -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 = «автоподтверждение: без замечаний»` diff --git a/openspec/changes/auto-confirm-clean-documents/specs/osv-result-mapping/spec.md b/openspec/changes/auto-confirm-clean-documents/specs/osv-result-mapping/spec.md new file mode 100644 index 0000000..ca6f48b --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/specs/osv-result-mapping/spec.md @@ -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 = «автоподтверждение документа »` (счёт — только у +ОСВ). Если `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` с шапкой, строк нет (колонок в снимке тоже нет — создаются те, что есть в результате) diff --git a/openspec/changes/auto-confirm-clean-documents/specs/primary-files/spec.md b/openspec/changes/auto-confirm-clean-documents/specs/primary-files/spec.md new file mode 100644 index 0000000..7a7ba6b --- /dev/null +++ b/openspec/changes/auto-confirm-clean-documents/specs/primary-files/spec.md @@ -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 = []`