diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index 75eaa6d..372fda9 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -573,13 +573,13 @@ NEEDS_CHECK`; пройдено/акцептовано + нажато «Подт (`current_recognition_job_id`): `PROCESSING` — ack сервиса (или результат `done`/`needs_check`, если ack потерян); `ERROR` — задание `error`/`timeout`/`dlq` либо ошибка маппинга; `PROCESSED` — маппинг завершён; `SUPERSEDED` — автозамена. `PROCESSED` и `SUPERSEDED` событиями обмена не перезаписываются. Физическое удаление: ANL-020 запрещает его после -INITIATED; в Срезе 2c консолидаций ещё нет, поэтому удаление разрешено, кроме файла с `CONFIRMED` документом и файла, +INITIATED; в Срезе 2c консолидаций ещё нет, поэтому удаление разрешено, кроме файла с документом, подтверждённым пользователем, автоподтверждённого документа в версии вне INITIATED/DOCUMENTS_PROCESSING/DATA_CORRECTION и файла, заменившего другие (§9.5); поведение после появления консолидаций — OQ-110. ### 9.3 Документ (`document_status`): создаётся ядром при маппинге результата, по одному на лист ОСВ: `RECOGNIZED` — задание `done`, `checks` парсера без нарушений, `counterparty_check ≠ MISMATCH` и нет предупреждений маппинга **этого листа** (повтор счёта в файле, строки без родителя, нарастающая по данным Q1); иначе `NEEDS_CHECK` -(OQ-05 закрыт: `RECOGNIZED` нужен). **Автоподтверждение (OQ-157, решение владельца 08.10.2026; ТЗ 03_9 п. 4, 10_10):** документ, получивший при маппинге `RECOGNIZED`, в той же транзакции становится `CONFIRMED` от имени системы (`confirmed_by` NULL, `audit_event(DOCUMENT_CONFIRM)` с `user_id` NULL и описанием «автоподтверждение: без замечаний»), привязывается к версиям и двигает их, как при ручном подтверждении (§3 п. 1); переход версии — от `stored_file.uploaded_by` с комментарием «автоподтверждение документа …». Форма ББ/ОФР — только при условиях ручного подтверждения (п. 4); `RECOGNIZED` формы после правки или акцепта — только вручную. `NEEDS_CHECK` — только вручную; «Подтвердить данные» → `CONFIRMED`; `ERROR` — зарезервирован (в Срезе 2c ядро +(OQ-05 закрыт: `RECOGNIZED` нужен). **Автоподтверждение (OQ-157, решение владельца 08.10.2026; ТЗ 03_9 п. 4, 10_10):** документ, получивший при маппинге `RECOGNIZED`, в той же транзакции становится `CONFIRMED` от имени системы (`confirmed_by` NULL, `audit_event(DOCUMENT_CONFIRM)` с `user_id` NULL и описанием «автоподтверждение: без замечаний»), привязывается к версиям и двигает их, как при ручном подтверждении (§3 п. 1); переход версии — от `stored_file.uploaded_by` с комментарием «автоподтверждение документа …». Форма ББ/ОФР — при тех же условиях, что ручное подтверждение (п. 4: период определён, нет непройденных жёстких проверок §7; мягкие не учитываются); `RECOGNIZED` формы после правки или акцепта — только вручную. `NEEDS_CHECK` — только вручную. `DISCLOSURE`/`OTHER` (без данных) подтверждаются автоматически тоже (в версии не привязываются; удаление — по §9.5 п. 5). «Подтвердить данные» → `CONFIRMED`; `ERROR` — зарезервирован (в Срезе 2c ядро документ в `ERROR` не создаёт: файл без разбираемой ОСВ получает `ERROR` на уровне файла). `counterparty_check`: ИНН шапки равен ИНН владельца файла → `MATCH`; ИНН нет — нормализованное имя шапки совпало с именем или алиасом владельца → `MATCH`; иначе `MISMATCH`; пустая шапка → `UNKNOWN`. `period_check` — `UNKNOWN`: подходящесть документа версии — функция (документ, версия), а один документ входит в несколько версий (§3 п. 1, Срез 4; OQ-120). @@ -591,7 +591,7 @@ INITIATED; в Срезе 2c консолидаций ещё нет, поэтом `NEEDS_CHECK`. Статус пересчитывается при маппинге, после каждой правки формы и после акцепта (кроме `CONFIRMED`); при пересчёте после правки/акцепта предупреждения маппинга не учитываются — на уровне документа они не хранятся (`metrics.mapping_warnings` — на уровне файла). Расшифровки (`DISCLOSURE`) и прочие (`OTHER`) — один документ на тип на -файл без данных, статус `RECOGNIZED` (OQ-23). Документ ББ/ОФР, созданный до Среза 3b и не имеющий данных формы +файл без данных, статус `RECOGNIZED` при маппинге, затем автоподтверждается → `CONFIRMED` (OQ-23, OQ-157). Документ ББ/ОФР, созданный до Среза 3b и не имеющий данных формы (`financial_statement_meta` нет), подтверждается без условий п. 9.5.4, а `GET`/`PATCH …/statement` по нему — 409 `DOCUMENT_NOT_STATEMENT`. @@ -658,11 +658,11 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; ` уверенность значений подтверждение **не блокируют** (как у ОСВ): защита — статус `NEEDS_CHECK` и подсветка. Мягкие проверки (в том числе межформенная) не блокируют. `DISCLOSURE`/`OTHER` — без условий. Привязка к версиям и переходы статуса версии — как у ОСВ (§3 п. 1, §9.1): ББ/ОФР за квартал версии (`Q1` — только версии за `Q1`, R-14a Среза 4). -5. **Удаление** (`DELETE /files/{id}`, `DOCUMENT_EDIT`): запрещено, если у файла есть документ, подтверждённый пользователем (`confirmed_by` не NULL), или автоподтверждённый документ привязан к версии вне `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION` (`FILE_HAS_CONFIRMED_DOCUMENTS`; OQ-157, уточняет OQ-110); привязки автоподтверждённых документов снимаются, статус версий не меняется, `FILE_DELETE.after_value.unbound_version_ids` и если файл заменил другие (`FILE_SUPERSEDES_OTHERS`); `SUPERSEDED` файл удалить +5. **Удаление** (`DELETE /files/{id}`, `DOCUMENT_EDIT`): запрещено, если у файла есть документ, подтверждённый пользователем (`confirmed_by` не NULL), или автоподтверждённый документ привязан к версии вне `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION` (`FILE_HAS_CONFIRMED_DOCUMENTS`; OQ-157, уточняет OQ-110); привязки автоподтверждённых документов снимаются, статус версий не меняется, `FILE_DELETE.after_value.unbound_version_ids`; запрещено также, если файл заменил другие (`FILE_SUPERSEDES_OTHERS`); `SUPERSEDED` файл удалить можно. Удаляются строки, документы (вместе с данными форм `financial_statement_*` и комментариями документов), задания и `stored_file`; объектам S3 ставится `expires_at = now()` (физически не стираются — OQ-111); аудит `FILE_DELETE` с карточкой файла в `before_value`. **OQ-110 закрыт для Среза 4:** к версии привязываются только `CONFIRMED` документы, а файл с таким документом удалить нельзя — удалить файл с документом в - версии невозможно; расширять удаление не требуется. + версии невозможно; расширять удаление не требуется (уточнено OQ-157: для автоподтверждённых документов — см. выше). 6. **Аудит.** `FILE_UPLOAD`, `FILE_REPLACE`, `FILE_DELETE`, `DOCUMENT_CONFIRM` (раздел `PRIMARY_DOCUMENTS`); правка формы и акцепт проверок — `RECOGNIZED_DATA_EDIT` (раздел `RECOGNITION`, `object_type = document`, `before/after` изменённых полей и ячеек); смены статусов задания/файла в обмене и в цикле маппинга `audit_event` не пишут (OQ-55). Не аудируются (Срез 3b): diff --git a/context/07-open-questions.md b/context/07-open-questions.md index 504d5eb..b2717a4 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -10,7 +10,7 @@ | OQ-02 | ОСВ «за 2/3/4 квартал» — квартальная или нарастающая? Как определять автоматически | FNR01 маппинг периода | срез 2, §2 правил | **закрыт решением владельца 05.10.2026:** ОСВ «за N квартал» — **квартальная**; признак сверяется по данным: если есть ОСВ Q1 того же года и `opening` совпадает — нарастающая (парсер ставит `is_cumulative` по тексту, ядро переопределяет при маппинге/в консолидации); **допущение парсера (Срез 2a):** `period_quarter` и `is_cumulative` вычисляются только по тексту периода (`rules/periods.yaml`; Q2–Q4 «N квартал» → `false`), сверка по данным — в ядре, подсрез 2c (`ASSUMPTION(OQ-02)`) | | OQ-03 | Приведение квартальных ОСВ к нарастающим: ключ соединения (контрагент vs договор), пропуск промежуточного квартала | DOC-259006 | срез 4 | **закрыт решением владельца 06.10.2026 (Срез 4):** ключ склейки — (счёт, контрагент по сырому имени и ИНН), договоры внутри контрагента (строка COUNTERPARTY уже содержит их сумму); пропуск квартала — **пробел** по паре (ЮЛ, счёт), а не блокирующая ошибка: строк пары в своде нет, пробел отдаётся в `gaps[]` сводки и в листе «Пробелы» экспорта; квартал без строки ключа = нули; дубль ключа внутри квартала суммируется. Правило — `05-domain-rules.md` §2; код — `domain/consolidation_periods.select_sources`, `domain/consolidated_osv.build` | | OQ-04 | ОСВ по субсчёту (58.03 вместо 58) закрывает ли обязательный документ «ОСВ 58» | фикстуры | чек-лист | **закрыт решением владельца 05.10.2026:** **нет**, ОСВ по субсчёту (58.03) — только субсчёт, обязательный документ «ОСВ 58» закрывает только полная ОСВ по счёту 58; чек-лист считает документ выполненным при `osv_account_code = код правила` | -| OQ-05 | Нужен ли статус `document.RECOGNIZED` или все идут через `NEEDS_CHECK → CONFIRMED` | DBML | срез 2–3 | **закрыт решением владельца 05.10.2026:** статус `RECOGNIZED` **нужен**: документ после успешного распознавания — `RECOGNIZED`, при нарушениях — `NEEDS_CHECK`, после подтверждения аналитиком — `CONFIRMED` Пересмотрено OQ-157 (08.10.2026): после маппинга чистый документ сразу `CONFIRMED`; `RECOGNIZED` остаётся у старых документов и форм после правки. | +| OQ-05 | Нужен ли статус `document.RECOGNIZED` или все идут через `NEEDS_CHECK → CONFIRMED` | DBML | срез 2–3 | **закрыт решением владельца 05.10.2026:** статус `RECOGNIZED` **нужен**: документ после успешного распознавания — `RECOGNIZED`, при нарушениях — `NEEDS_CHECK`, после подтверждения аналитиком — `CONFIRMED`. Пересмотрено OQ-157 (08.10.2026): после маппинга чистый документ сразу `CONFIRMED`; `RECOGNIZED` остаётся у старых документов и форм после правки. | | OQ-06 | Замена одного документа внутри многодокументного файла | DBML | срез 3 | решено: замена файлом целиком | | OQ-07 | Матчинг ВГО: одна строка = одна позиция (DOC-223044) или агрегировать по (компания, контрагент, счёт) (FNR03)? | DOC-223044 vs FNR03 | срез 5 | допущение: агрегировать по (компания, контрагент, счёт, сторона) с детализацией по `osv_entry.id` | | OQ-08 | Знак порога 5 % в правиле пары (≤ или <), и к чему считать % — к меньшей сумме (Методика сноска 14) | FNR03 (знак потерян) | срез 5 | допущение: `≤ 5 %` к меньшей | @@ -115,7 +115,7 @@ | OQ-107 | При `hierarchy=sums` (дерево восстановлено по равенству «сумма детей = родитель») сверка «сумма детей = родитель» тавтологична — она не может не сойтись. Считать ли такой разбор `needs_check` (или отдельное предупреждение достаточно)? | tabular-parser: финальное ревью Среза 2a, M-5 | срез 2a+ (статус результата), срез 2c (UI проверки) | допущение принято: статус — по контрольным суммам, стратегия видна в `metrics.warnings` (`hierarchy=sums`); на реальных файлах стратегия не выбирается (все — `outline`/`trivial`) | | OQ-108 | Защита фазы чтения xlsx от XML-бомб (entity expansion) — `defusedxml` как новая зависимость сервиса — и иные лимиты памяти чтения. Остаточные риски после R-23/R-25: (1) бомба по объёму самих ячеек в пределах лимита распакованного zip — xlsx в 5 МБ с 2 млн обычных ячеек читается openpyxl 13 с и занимает 1,5 ГБ (замер ревьюера); (2) объём `sharedStrings` и стилей в тех же пределах; (3) entity expansion (от экспоненты «billion laughs» защищает только сам expat ≥ 2.4); (4) **известный обход предсканирования (R-26):** лист в части без `.xml` с ≥ 64 байтами ведущих пробелов и без XML-декларации не попадает в скан (файл 1,8 КБ → 27 с / 1,4 ГБ в openpyxl) — первый follow-up: убрать «sniff» и отдавать expat каждую некаталожную запись; (5) скан сам не ограничен размером чанка: «мусорная» XML-часть с атрибутом в 400 МБ → 1,3 ГБ / 1,2 с, 400 МБ тегов → 8,5 с CPU; (6) xls с очень многими крупными merged-диапазонами (> 10 000 ячеек каждый) — линейный просмотр индекса `Sheet` (≈ 4,4 мс на обращение), останавливает только `PARSE_TIMEOUT_SECONDS`: потолок на число таких диапазонов. Предскан — эшелон защиты, не граница безопасности; настоящая граница — лимит памяти контейнера / подпроцесс с RLIMIT. Рекомендация: жёсткий лимит памяти контейнера сервиса; и/или предпроверка объявленного `` и потоковый счётчик ячеек ``/строк `sharedStrings` (тем же expat-сканом, что объединения) с лимитом; и/или разбор книги в подпроцессе с `RLIMIT_AS`/таймаутом, который можно убить. Решение платформы | tabular-parser: финальное ревью Среза 2a, I-1 (хвост), R-23, R-25 | tabular-parser (readers), платформа (зависимости и лимиты образа/пода) | допущение принято: действуют лимиты R-23/R-25 — распакованный zip ≤ 10 × `PARSE_MAX_BYTES`, площадь объединённых ячеек книги ≤ 200 000 (потоковый expat-скан всех XML-частей до openpyxl); объём ячеек, `sharedStrings`/стили и entity expansion не ограничены (README «Известные ограничения») | | OQ-109 | Воркер tabular-parser скачивает объект S3 целиком и только потом проверяет `PARSE_MAX_BYTES`; проверять размер по `stat` до скачивания | tabular-parser: финальное ревью Среза 2a, M-7 | tabular-parser (`core/worker.py`) — вне Среза 2a | **закрыт реализацией Среза 2c (06.10.2026, `arm-core/openspec/changes/slice-2c-osv-ingest`):** ядро ограничивает размер файла на загрузке (`UPLOAD_MAX_BYTES` = 50 МиБ, как `PARSE_MAX_BYTES` парсера), поэтому в воркер большие объекты из ядра не попадают; проверка по `stat` до скачивания в `tabular-parser` остаётся как есть (дефенсивная, не требуется) | -| OQ-110 | Удаление файла после консолидаций: у `document` появятся FK из `financial_statement_meta`, `consolidation_document`, `ex_snapshot_item`, а `DELETE /files/{id}` Среза 2c их не чистит (в 2c строк в этих таблицах нет). ANL-020 запрещает физическое удаление после INITIATED. Что делать: запретить удаление файла, документ которого входит в версию консолидации, и/или каскадно чистить служебные таблицы (`financial_statement_meta` — в Срезе 3) | arm-core Срез 2c: Task 10 (R-23, design №36, №48) | Срезы 3, 4 (расширить удаление или запрет) | **закрыт для Среза 4 (06.10.2026):** привязываются к версии только `CONFIRMED` документы незаменённых файлов, а файл с `CONFIRMED` документом удалить нельзя (`FILE_HAS_CONFIRMED_DOCUMENTS`) — удаление файла с документом в версии невозможно, расширять `DELETE /files/{id}` не нужно; автозамена снимает привязки документов заменённого файла (`FILE_REPLACE.unbound_version_ids`). Остаток: FK из `financial_statement_meta` и `ex_snapshot_item` — Срезы 3 и 7 Уточнено OQ-157: файл с автоподтверждёнными документами удаляется, пока их версии «в работе»; привязки снимаются. | +| OQ-110 | Удаление файла после консолидаций: у `document` появятся FK из `financial_statement_meta`, `consolidation_document`, `ex_snapshot_item`, а `DELETE /files/{id}` Среза 2c их не чистит (в 2c строк в этих таблицах нет). ANL-020 запрещает физическое удаление после INITIATED. Что делать: запретить удаление файла, документ которого входит в версию консолидации, и/или каскадно чистить служебные таблицы (`financial_statement_meta` — в Срезе 3) | arm-core Срез 2c: Task 10 (R-23, design №36, №48) | Срезы 3, 4 (расширить удаление или запрет) | **закрыт для Среза 4 (06.10.2026):** привязываются к версии только `CONFIRMED` документы незаменённых файлов, а файл с `CONFIRMED` документом удалить нельзя (`FILE_HAS_CONFIRMED_DOCUMENTS`) — удаление файла с документом в версии невозможно, расширять `DELETE /files/{id}` не нужно; автозамена снимает привязки документов заменённого файла (`FILE_REPLACE.unbound_version_ids`). Остаток: FK из `financial_statement_meta` и `ex_snapshot_item` — Срезы 3 и 7. Уточнено OQ-157: файл с автоподтверждёнными документами удаляется, пока их версии «в работе»; привязки снимаются. | | OQ-111 | Ретеншн S3-объектов при удалении файла: сейчас `DELETE` лишь ставит `s3_object.expires_at = now()`, объекты `uploads`/`results` физически не стираются (дополняет OQ-22). Кто и когда их удаляет — lifecycle-правило бакета по метаданным, фоновая задача ядра, ручная очистка? Объект, записанный в S3 до сбоя БД, остаётся сиротой | arm-core Срез 2c: design D-10, №36 | платформа (lifecycle), Срез 2 → эксплуатация | допущение принято: бакет растёт; очистка — вне среза | | OQ-112 | Остаточные объекты e2e-прогона: каждый запуск `make e2e` оставляет в MinIO dev-стенда ~2 объекта (`uploads/…`, `results/…` — следствие OQ-111) | arm-core Срез 2c: Task 11 (O-5) | dev-стенд | допущение принято: чистить бакет `arm-ka` вручную (раздел «Сброс стенда» `dev/qa-guide.md`) | | OQ-113 | Полнота плана счетов seed: 19 нормализованных кодов строк-счетов golden ОСВ отсутствуют в `chart_of_account` (`01.01`, `66.01`, `76.01`, `60.АВ`, `76.ОТ` и др.), поэтому у их строк `account_id = NULL`. Достаточно ли «администратор добавит» или seed должен включать полный набор субсчетов (включая буквенные) до Среза ВГО (§4–§5) | arm-core Срез 2c: Task 8 (O-3) | Срезы 4–5 (пары счетов элиминации) | **закрыт: допущение принято — выгрузка плана счетов 1С заказчика (`fixtures/directories/chart-of-accounts-1c.xlsx`, 488 строк, все 122 кода seed в ней есть) полная (arm-core #25);** ранее: частично решён 06.10.2026, перенос в seed — arm-core#23 | diff --git a/dev/api-contract-files-directories.md b/dev/api-contract-files-directories.md index d2203b5..207d6b4 100644 --- a/dev/api-contract-files-directories.md +++ b/dev/api-contract-files-directories.md @@ -66,7 +66,7 @@ opening_debit?, opening_credit?, turnover_debit?, turnover_credit?, closing_debi всегда `UNKNOWN` → показывайте «—» (OQ-120). - «Статус» (подписи — как в чек-листе и его CSV, `context/10-user-journey.md` «Словарь UI»): файл `UPLOADED`/`PROCESSING` — «Обработка»; файл `ERROR` — «Ошибка» (причина — `job.error_message` / `job.mapping_error`; задание `timeout`/`dlq` — тоже - ошибка, причина в `job.error_message`); иначе по документу: `RECOGNIZED` и `NEEDS_CHECK` — «Требуется проверка», `CONFIRMED` — + ошибка, причина в `job.error_message`); иначе по документу: `NEEDS_CHECK` и `RECOGNIZED` (старые и формы после правки) — «Требуется проверка», `CONFIRMED` (в т.ч. автоподтверждённый) — «Загружен», `ERROR` — «Ошибка». - Комментарий — у **документа** (`comments_count`, `GET/POST /documents/{id}/comments`, контракт 3b); у файла без документов комментария нет. diff --git a/dev/frontend-api-guide.md b/dev/frontend-api-guide.md index 5163014..2d6317a 100644 --- a/dev/frontend-api-guide.md +++ b/dev/frontend-api-guide.md @@ -209,7 +209,7 @@ function parseError(status: number, body: unknown): { status: number; code: stri | `PermissionObject` / `PermissionAction` | `DIRECTORY`, `DOCUMENT`, `CONSOLIDATION`, `KFO`, `EX`, `EXPORT`, `USERS`, `AUDIT` / `VIEW`, `EDIT`, `APPROVE`, `VERIFY`, `DELETE`, `EXPORT`, `ADMIN` | | `OnExisting` (импорт) | `skip`, `update` | -**Автоподтверждение документа (OQ-157).** После маппинга чистый документ сразу приходит `CONFIRMED` с `confirmed_by: null`: кнопку «Подтвердить» показывать неактивной; повторный `POST …/confirm` — 409 `ALREADY_CONFIRMED`. Удаление файла с таким документом возможно (204), кроме случаев, когда документ подтвердил пользователь или версия на согласовании: тогда 409 `FILE_HAS_CONFIRMED_DOCUMENTS`. +**Автоподтверждение документа (OQ-157).** После маппинга чистый документ сразу приходит `CONFIRMED` с `confirmed_by: null`: кнопку «Подтвердить» показывать неактивной; повторный `POST …/confirm` — 409 `ALREADY_CONFIRMED`. Удаление файла с таким документом возможно (204), кроме случаев, когда документ подтвердил пользователь или документ привязан к версии вне корректировки данных (не `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION`): тогда 409 `FILE_HAS_CONFIRMED_DOCUMENTS`. --- @@ -350,7 +350,7 @@ UI прячет элементы по `GET /me → permissions` (объедин |---|---|---| | `UPLOADED` | принят, ждёт отправки на распознавание | «Обработка», опрашивать | | `PROCESSING` | распознаётся или маппится; `documents[]` ещё пуст | «Обработка», опрашивать | -| `PROCESSED` | документы созданы | статус — по документу: `RECOGNIZED`/`NEEDS_CHECK` «Требуется проверка», `CONFIRMED` «Загружен», `ERROR` «Ошибка» | +| `PROCESSED` | документы созданы | статус — по документу: `NEEDS_CHECK` и `RECOGNIZED` (старые и формы после правки) «Требуется проверка», `CONFIRMED` «Загружен» (в т.ч. автоподтверждённый), `ERROR` «Ошибка» | | `ERROR` | ошибка распознавания (`job.status` `error`/`timeout`/`dlq`, причина — `job.error_code`/`error_message`) или маппинга (`job.mapping_error`) | «Ошибка» + причина | | `SUPERSEDED` | заменён более новым файлом с теми же документами (автозамена); в списке скрыт без `include_superseded=true` | — | @@ -363,7 +363,7 @@ UI прячет элементы по `GET /me → permissions` (объедин Документ: - Один файл = N документов: лист ОСВ по счёту — документ `OSV` (`osv_account_code`, `locator` — лист), форма ББ/ОФР — документ - `BALANCE`/`INCOME`, расшифровки — один документ `DISCLOSURE`/`OTHER` на тип (без данных, сразу `RECOGNIZED`). + `BALANCE`/`INCOME`, расшифровки — один документ `DISCLOSURE`/`OTHER` на тип (без данных; статус `RECOGNIZED` при маппинге, затем автоподтверждается → `CONFIRMED`). - Период: `period_year` + `period_quarter` + `is_cumulative`. Квартальный — «2024 К1», нарастающий — «2024 · 9 мес.»; `period_quarter = null` — «период не определён» (например, ОСВ со скользящим периодом) — не подставлять квартал самим. - Проверки: `counterparty_check` — сверка шапки с ЮЛ-владельцем; `period_check` **всегда `UNKNOWN`** — показывать «—» @@ -593,7 +593,7 @@ async function download(path: string, fallbackName: string): Promise { | `DOCUMENT_READONLY` | 409 | правка/акцепт подтверждённой формы | — | перевести экран в просмотр | | `UNKNOWN_CELL`, `DUPLICATE_CELL` | 422 | правка формы | — | ошибка клиента (перечитать форму) | | `NOTHING_TO_ACCEPT` | 409 | акцепт без нарушений | — | перечитать форму | -| `FILE_HAS_CONFIRMED_DOCUMENTS`, `FILE_SUPERSEDES_OTHERS` | 409 | удаление файла | — | скрывать «Удалить» у таких файлов (есть документ, подтверждённый пользователем, или автоподтверждённый документ в версии на согласовании, см. §3.7; или на файл ссылаются `superseded_by_file_id`) | +| `FILE_HAS_CONFIRMED_DOCUMENTS`, `FILE_SUPERSEDES_OTHERS` | 409 | удаление файла | — | скрывать «Удалить» у таких файлов (есть документ, подтверждённый пользователем, или автоподтверждённый документ в версии вне корректировки данных (не `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION`), см. §3.7; или на файл ссылаются `superseded_by_file_id`) | | `FILE_CONTENT_NOT_FOUND` | 404 | первичка | — | «Файл больше недоступен» | | `TOO_MANY_IDS`, `INVALID_IDS` | 422 | `users/brief` | — | ошибка клиента (бейте на пачки ≤ 200) | | `CONSOLIDATION_EXISTS` | 409 | создание консолидации | — | «Консолидация за этот период уже есть» → открыть её | diff --git a/dev/qa-guide.md b/dev/qa-guide.md index 1a0da3f..0f04750 100644 --- a/dev/qa-guide.md +++ b/dev/qa-guide.md @@ -506,6 +506,8 @@ PDF и изображения обрабатывает `scan-gateway`; ядро ### 6.9 Формы ББ/ОФР: просмотр, правка, акцепт, подтверждение (Срез 3b) +Предусловие (OQ-157): чистая форма после маппинга сразу `CONFIRMED` (`confirmed_by` пуст, `reviewed_by` NULL) и правке не поддаётся (`PATCH` — 409 `DOCUMENT_READONLY`). Поэтому F1–F2 — на чистой форме `1_Форма1_бух_09.xlsx`; затем удалите её (`DELETE /files/{file_id}`, 204) и загрузите копию с нарушением: в Excel сумма строки 1600 колонки 0 увеличена на 100 (`1600 = 1100 + 1200` нарушено). F3–F10 ведутся на этой копии (`file_id` новой формы); F3 увеличивает сумму от значения копии, F7 возвращает её на верную сумму `1100 + 1200`. + Предусловия: раздел 4.2 (ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`), `make seed-dev` (dev-ГК «Агрофирма Межениновская», ЮЛ с ИНН `7016006635`, Ростовский РФ — ИНН совпадает с ИНН шапок форм), пользователь `ka_go` (`DOCUMENT_EDIT`, `DOCUMENT_VIEW`, видит всё) и для негатива `expert` (только `DOCUMENT_VIEW`), `client_manager` (чужой филиал). В UI правка, акцепт и подтверждение @@ -530,23 +532,23 @@ curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m js | № | Пользователь | Шаги | Ожидание | |---|---|---|---| -| F1 | ka_go | загрузить `1_Форма1_бух_09.xlsx` для ЮЛ с ИНН 7016006635, дождаться `PROCESSED` | один документ: `document_type = BALANCE`, `osv_account_code` пуст, `period_year = 2025`, `period_quarter = Q3`, `counterparty_check = MATCH`, `comments_count = 0` | +| F1 | ka_go | загрузить `1_Форма1_бух_09.xlsx` для ЮЛ с ИНН 7016006635, дождаться `PROCESSED` | один документ: `document_type = BALANCE`, `osv_account_code` пуст, `period_year = 2025`, `period_quarter = Q3`, `counterparty_check = MATCH`, `comments_count = 0`, `status = CONFIRMED` (автоподтверждение: `confirmed_by` пуст, `reviewed_by` NULL) | | F2 | ka_go | `GET /documents/{id}/statement` | 200; `edition = fsbu4`; `document.reporting_date = 2025-09-30`; `columns` — 3 (у колонки 0 `is_current = true`); `lines` — 37, отсортированы по `line_code`, у каждой `values` на все колонки; суммы — строки; `header.inn.value = "7016006635"`, `confidence = "100.00"`, `modified = false`; `checks.failed`, `soft_failed` пусты, `accepted = null` | | F3 | ka_go | `PATCH …/statement` `{"values":[{"line_id":…,"column_id":…,"amount":"<сумма строки 1600, колонки 0, увеличенная на 100>"}]}` (id — из F2) | 200; у ячейки `amount` новая, `original_amount` = прежняя, `is_modified = true`, `modified_by` = пользователь; `checks.failed` непуст (нарушено `1600 = 1100 + 1200` и/или `1600 = 1700`), `document.status = NEEDS_CHECK`; в `audit_event` одно `RECOGNIZED_DATA_EDIT` (раздел `RECOGNITION`, `object_type = document`) с `before`/`after` ячейки (6.4) | | F4 | ka_go | `POST …/confirm` | 409 `CHECKS_NOT_PASSED`, `detail.failures[]` — нарушения (`column_index`, `relation`, `expected`, `actual`) | | F5 | ka_go | `POST …/statement/checks/accept` `{"comment":"округление в форме клиента"}` | 200; `checks.accepted = {by, at, comment}`, `checks.failed` по-прежнему непуст; статус документа пересчитан (`RECOGNIZED`, если других причин `NEEDS_CHECK` нет); `POST …/confirm` теперь проходит (но F11 делайте после возврата значений) | | F6 | ka_go | снова `PATCH` той же ячейки другой суммой | 200; `checks.accepted = null` (акцепт сброшен правкой) | -| F7 | ka_go | `PATCH` той же ячейки обратно на исходную сумму (`original_amount` из F3) | 200; `is_modified = false`, `original_amount` остаётся, `checks.failed` пуст; `PATCH` с пустым телом `{}` — 200, новых записей `RECOGNIZED_DATA_EDIT` нет | +| F7 | ka_go | `PATCH` той же ячейки на верную сумму (`1100 + 1200` по строкам 1100 и 1200 этой формы) | 200; `checks.failed` пуст; `document.status = RECOGNIZED` (ручное подтверждение — F11); `original_amount` остаётся сломанной суммой из копии, `is_modified = true`; `PATCH` с пустым телом `{}` — 200, новых записей `RECOGNIZED_DATA_EDIT` нет | | F8 | ka_go | `PATCH` `{"header":{"inn":"123"}}` и `{"header":{"inn":null}}`; `{"values":[{"line_id":"<случайный uuid>","column_id":"<случайный uuid>","amount":"1"}]}` | 422 `INVALID_INN` (оба случая); 422 `UNKNOWN_CELL`; данные не изменились | | F9 | ka_go | `PATCH` `{"header":{"report_date":"2025-06-30"}}`, затем вернуть `"2025-09-30"` | период пересчитывается: `period_quarter = Q2` (`reporting_date = 2025-06-30`), `header.report_date.modified = true`, `original_value` не меняется (после возврата на `2025-09-30` — `Q3`, `modified` остаётся `true`); дата не на конец квартала (`2025-09-15`) — 200, `period_quarter = null`, статус `NEEDS_CHECK`, `POST …/confirm` — 409 `PERIOD_REQUIRED` (поправьте дату — подтверждение проходит) | | F10 | ka_go | `POST …/accept` без нарушений | 409 `NOTHING_TO_ACCEPT` | -| F11 | ka_go | `POST …/confirm` для чистой формы (после F7/F9 вернуть исходные значения) | 200 `DocumentOut`, `status = CONFIRMED`, `confirmed_by`/`confirmed_at`; повтор — 409 `ALREADY_CONFIRMED`; `PATCH`/`accept` — 409 `DOCUMENT_READONLY`; в БД у шапки `reviewed_by/at` заполнены | -| F12 | ka_go | загрузить `1_Форма2_бух_09.xlsx` тому же ЮЛ, дождаться `PROCESSED`; `GET …/statement` | `document_type = INCOME`, `period_quarter = Q3`, 2 колонки, `edition = fsbu4`; у ББ и ОФР поле `checks.cross_form` — либо `null`, либо нарушение `1370 − 1370(начало года) = 2400` (мягкое, не блокирует подтверждение); статус ОФР — `RECOGNIZED` | +| F11 | ka_go | `POST …/confirm` для формы в `RECOGNIZED` после F7 и F9 (ручное подтверждение) | 200 `DocumentOut`, `status = CONFIRMED`, `confirmed_by`/`confirmed_at`; повтор — 409 `ALREADY_CONFIRMED`; `PATCH`/`accept` — 409 `DOCUMENT_READONLY`; в БД у шапки `reviewed_by/at` заполнены. Для автоподтверждённой чистой формы (F1, до удаления) `POST …/confirm` — 409 `ALREADY_CONFIRMED`, `confirmed_by` пуст | +| F12 | ka_go | загрузить `1_Форма2_бух_09.xlsx` тому же ЮЛ, дождаться `PROCESSED`; `GET …/statement` | `document_type = INCOME`, `period_quarter = Q3`, 2 колонки, `edition = fsbu4`; у ББ и ОФР поле `checks.cross_form` — либо `null`, либо нарушение `1370 − 1370(начало года) = 2400` (мягкое, не блокирует подтверждение); статус ОФР — `CONFIRMED` (автоподтверждение; мягкие проверки его не отменяют) | | F13 | ka_go | `GET /files/{file_id}/content?disposition=inline` и без параметра | 200, тело байт в байт равно загруженному `.xlsx`, `Content-Type` xlsx, заголовки `X-Content-Type-Options: nosniff` и `Cache-Control: private, no-store`; `inline` — ещё `Content-Security-Policy: sandbox`, `Content-Disposition: inline; filename="file.xlsx"; filename*=UTF-8''<имя>` (кириллица в percent-кодировке); `disposition=foo` — 422 | | F14 | ka_go | `POST /documents/{id}/comments` `{"text":"нет подписи руководителя"}`; `{"text":" "}`; затем `GET …/comments` и `GET /files/{file_id}` | 201 с `author.full_name`; пустой после обрезки текст — 422; в списке один комментарий; в карточке файла и `GET /documents/{id}` у документа `comments_count = 1`; комментарий к `CONFIRMED` документу разрешён | | F15 | ka_go | `GET /users/brief?ids=,<случайный uuid>`; с 201 id; с `ids=abc` | 200, один элемент `{id, full_name, is_active}`; 422 `TOO_MANY_IDS`; 422 `INVALID_IDS`; то же под `expert` (без `USERS_ADMIN`) — 200 | | F16 | ka_go | `GET /directories/legal-entities?q=7016006635` после F1 и F12 | `documents_count = 2` (ББ и ОФР) | -| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; подтвердить ББ и ОФР (F11, F12); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename="checklist.csv"` и `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` | +| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; убедиться, что ББ и ОФР второго ЮЛ `CONFIRMED` (чистые формы автоподтверждаются; при `NEEDS_CHECK` — проверить значения и подтвердить вручную, F11); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename="checklist.csv"` и `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` | | F18 | expert | `PATCH …/statement`, `POST …/comments`, `POST …/accept` | 403 (нет `DOCUMENT_EDIT`); `GET …/statement` и `GET …/comments` — 200 | | F19 | client_manager | `GET …/statement`, `GET /files/{id}/content` документа ЮЛ другого филиала | 404 `NOT_FOUND`, тело как у случайного uuid | | F20 | ka_go | `GET …/statement` документа `OSV` (раздел 6.6) | 409 `DOCUMENT_NOT_STATEMENT` | @@ -766,7 +768,7 @@ docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --buil | № | Шаги | Ожидание | |---|---|---| | W16 | В панели Счастливочной клик по «ОСВ 60» | экран «Просмотр ОСВ»: крошки «Отчётность › ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО › Проверка документа», подзаголовок «<имя файла> · XLS · <дата>», справа статус «Загружен», кнопка «✓ Подтвердить» неактивна; надпись «Суммы в рублях; строки ОСВ только для чтения»; дерево счёт → субсчёт → контрагент, колонки «Счёт / контрагент», «ИНН» и шесть сумм. Корневая строка `60`: «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08» (сальдо нач. Кт + обороты Кт − обороты Дт = сальдо кон. Кт) | -| W17 | Загрузить ОСВ Счастливочной под ЮЛ РБ (один файл из `../fixtures/osv/schastl/2024-Q3/`, проверка `Контрагент ✗`), в панели открыть «ОСВ 60» → статус «Требуется проверка» → «✓ Подтвердить» | до подтверждения статус «Требуется проверка»; после — уведомление «Документ подтверждён», статус «Загружен», кнопка неактивна; в панели документ — «Загружен» | +| W17 | Загрузить один файл из `../fixtures/osv/schastl/2024-Q3/` в ЮЛ вне демо-периметра (любое ЮЛ стенда вне ГСК демо, без `consolidation_version_id`; проверка `Контрагент ✗` — шапка другой организации; не РБ, чтобы не заменить файлы и не сдвинуть счётчики W18), в панели открыть «ОСВ 60» → статус «Требуется проверка» → «✓ Подтвердить» | до подтверждения статус «Требуется проверка»; после — уведомление «Документ подтверждён», статус «Загружен», кнопка неактивна; в панели документ — «Загружен» | | W18 | Остальные файлы РБ и Счастливочной 2024 Q1–Q3 (всего 22, список — 7.1): через «Загрузить документы» у каждого ЮЛ и подтверждение каждого ОСВ, либо скриптом из каталога `arm-web`: `scripts/demo-prepare.sh ` (id — из адреса `?version=`) | скрипт печатает строку на каждый файл (пять файлов из W13 — «уже загружен», это нормально), ждёт распознавания (до 5 мин), подтверждает только неподтверждённые ОСВ (чистые уже «Загружен», автоподтверждение, OQ-157) и в конце печатает `строк: 1385 документов: 22 несопоставленных: 1381 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ | `demo-prepare.sh` берёт пользователя вторым аргументом (по умолчанию `ka_go@armka.local`), адрес ядра — из `API` @@ -822,7 +824,7 @@ PDF из `fixtures/ocr` — отчётность других компаний ( «Счёт» принимает только корень (`60`, не `60.01`). - Таблица консолидаций и статус версии обновляются по F5: после создания консолидации на странице той же ГК новой строки может не быть до обновления. Панель «Организации» перечитывается при закрытии панели документов. -- «Распознан» и «Требуется проверка» в панели документов выглядят одинаково — «Требуется проверка». +- «Распознан» (старые `RECOGNIZED` и формы после правки) и «Требуется проверка» в панели документов выглядят одинаково — «Требуется проверка». - «ГСК» и «Дата верификации» в таблице консолидаций, «Утверждён в РФ» и «Верифицирован в ГО» — всегда «—». - Панель документов показывает первые 100 файлов ЮЛ; загрузка — только из панели ЮЛ внутри версии. - Шапку формы (ИНН, дату, единицу) нельзя очистить, только заменить; время принятия нарушений — в часовом поясе