diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index 722fbab..39743bd 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -492,6 +492,15 @@ variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amou без даты; решение владельца 07.10.2026 по OQ-145 — редакция в контракт не передаётся, досрочный ФСБУ 4/2023 — новый OQ при первом случае); раздельные словари и формулы по редакциям — вместе с составом `fsbu4` (OQ-146, OQ-150). +**Полный состав формы (arm-core#80, решение владельца 09.10.2026).** Экран проверки показывает все строки формы +редакции документа (`edition`): справочник `arm-core/api/rules/statement_lines.yaml` (66н и ФСБУ 4/2023, ББ и ОФР, из +шаблонов банка `fixtures/statements-xlsx/templates/`). Строки и ячейки, которых нет в источнике, ядро достраивает в +ответе (`id = null`, `origin = template`) и в БД не пишет; первая непустая правка создаёт строку +(`line_code_original_value = NULL` — признак «не из источника») и ячейку как правку аналитика (аудит +`RECOGNIZED_DATA_EDIT`). Пустая добавленная строка не влияет на статус, проверки §7 и автоподтверждение; созданная +правкой строка участвует в проверках, пока у неё есть хоть одна сумма. Без редакции (нет даты периода) строки не +достраиваются. Строки «БАЛАНС» (1600, 1700) на экране выделяются цветом. + Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → `document.status = NEEDS_CHECK`; пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`. Отрицательные значения в формах — в скобках `(2100)` → `-2100`. Значения с `confidence < 100` подсвечиваются. @@ -874,6 +883,7 @@ PDF-пути, решение №1 спеки 3a; `pages:…` — только е `financial_statement_column` (из шапки `rows[0]`, если она есть; `period_date` распарсить из «На 30 июня 2025 г», `is_current` = первая; без шапки — колонки без дат, документ `NEEDS_CHECK`) + `financial_statement_line` (`cells[1]` код, `cells[0]` имя; только строки с кодом) + `financial_statement_value` (число: убрать пробелы/`-`, скобки → минус; `confidence`; `is_recognized`). +Недостающие строки формы ядро достраивает на экране по справочнику редакции (§7, arm-core#80). Сверки: ИНН/имя vs `legal_entity` → `document.counterparty_check`; дата vs период версии → `period_check`. ### §11.1 Очистка OCR-текста (перенос из старого ядра, Срез 3) diff --git a/context/07-open-questions.md b/context/07-open-questions.md index 85ce8f9..e4a9187 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -155,7 +155,7 @@ | OQ-147 | Закрывающая скобка, распознанная OCR как `}` или `]` (`(7744536}` в `aep_3.json`): дословный перенос `parse_amount` минус не ставит — сумма положительная, `is_recognized = false`. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде | change `slice-3a-ocr-normalizer`, решение №12; владелец 06.10.2026: «пока устроит как есть» | scan-gateway `normalizer/ocr/amounts.py`, §11.1 п. 7 | открыт: допущение — перенос дословный; знак теряется, строка подсвечена `is_recognized = false`, исходный текст — в `raw/` | | OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный `parse_amount` вырезает тире как «лишний» символ → сумма `null`, но `is_recognized = false` даже при `confidence = 100` (дефис «-» даёт `true`). Лишний `needs_check` на формах с длинным тире. Считать ли прочерк любого вида распознанным | change `slice-3a-ocr-normalizer`, решение №20 | scan-gateway `amounts.py`, §11.1 п. 7, доля `done` на реальных сканах | открыт: допущение — перенос дословный; правило уточняется после прогона на стенде | | OQ-149 | Строки таблицы OCR, отброшенные классификацией (поведение источника, §11.1 п. 4), не восстанавливаются: с непустым нечисловым кодом (`1150 :`) и с пустым кодом, но с суммами (в `aep_3.json` — «БАЛАНС» и «Выручка»: код пуст, мусор `211(1 \|` стоит в колонке значения). Нормализатор выдаёт предупреждения `строк с нечисловым кодом пропущено: N` и `строк без кода с суммами пропущено: N` (как OQ-141 для Excel), документ — `needs_check`, содержимое строк остаётся в `raw/`. Восстанавливать ли такие строки по наименованию (словарь редакции) или по структуре формы | `fixtures/ocr/aep_3.json`, `aep_6.json`, change `slice-3a-ocr-normalizer`, решение №29, §11.1 п. 4 | scan-gateway `rows.py`/`document.py`, полнота форм, OQ-36, OQ-139, OQ-141 | открыт: допущение — строки пропускаются с предупреждением; аналитик видит расхождение итогов в проверках 3b | -| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` | +| OQ-150 | Состав редакции `fsbu4` (ФСБУ 4/2023, отчётность с 2025 года) в `api/rules/statement_lines.yaml` scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны `fixtures/statements-xlsx/templates/`, OQ-142). Пока `lines: {}` | change `slice-3a-ocr-normalizer`, D-7 | scan-gateway `api/rules/`, эталоны `fixtures/ocr/expected_normalize/` для документов 2025 года, статус `done` | открыт: **данные даёт владелец**, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и `needs_check`. Порядок заполнения — `scan-gateway/README.md` **09.10.2026, решение владельца:** состав `fsbu4` (и `66n`) для ядра принят из шаблонов банка — `arm-core/api/rules/statement_lines.yaml` (arm-core#80); словарь scan-gateway — follow-up, копии расходятся (66н scan-gateway — словарь старого ядра). | | OQ-151 | Эмулятор ИС Сканирование: файл, совпавший по sha256 с PDF из `SCAN_FIXTURES_DIR`, у которого нет JSON-набора (`<имя>.json`), отвечает «нет пары» (`INVALID_DOCUMENT`) и **не** уходит на `SCAN_EMULATOR_DEFAULT`. Пробел спеки эмулятора: считать ли такой файл промахом и отдавать набор по умолчанию | change `slice-3a-ocr-normalizer`, спека `scan-emulator` | scan-gateway `scan_emulator`, dev-стенд (`dev/qa-guide.md` §6) | открыт: допущение — поведение как есть; PDF без JSON-пары в `fixtures/ocr` не класть | | OQ-152 | Повтор кода строки в таблице OCR: остаётся **первая** строка (паритет с Excel-путём, §12 п. 5; решение №15), но в `aep_6.json` (ОФР) код 2410 повторён, оставленная первая строка пуста, а отброшенная содержит `(649)`. Выбрать правило «первая строка с суммой» вместо «первая» (для `aep_3` оно даёт иной результат, чем для `aep_6` — нужна проверка на стенде) | change `slice-3a-ocr-normalizer`, решение №34 (R-16), финальное ревью I-2 | scan-gateway `document.py`, §11.1 п. 4, Excel-путь §12 п. 5 | открыт: допущение — первая строка; предупреждение «код N повторяется» сигнализирует аналитику, сумма отброшенной строки остаётся в `raw/` | | OQ-153 | Журналировать ли скачивание первички (`GET /files/{id}/content`) и CSV чек-листа (ТЗ 8.2: перечень событий журнала включает «выгрузку»)? Экспорт сводной ОСВ в xlsx пишет `audit_event(EXPORT)` (Срез 4), а скачивание исходного файла и CSV чек-листа в Срезе 3b — нет; комментарии к документу тоже не аудируются (`comment` хранит автора и время) | change `slice-3b-statements-api`, design D-10, D-11, D-13, решение №8; ТЗ 8.2 | `audit_event`, `GET /files/{id}/content`, `GET …/checklist?format=csv`, §9.5 п. 6 | **закрыт решением владельца 07.10.2026 (вариант А):** скачивание исходного файла (`GET /files/{id}/content`, это же и просмотр PDF) и CSV чек-листа в `audit_event` не пишутся; `EXPORT` пишет только экспорт сводной ОСВ в xlsx (Срез 4) | diff --git a/dev/api-contract-slice-3b.md b/dev/api-contract-slice-3b.md index d8e9e2f..79ece02 100644 --- a/dev/api-contract-slice-3b.md +++ b/dev/api-contract-slice-3b.md @@ -32,7 +32,7 @@ D-1…D-14, «Decisions made without discussion» №1–№22). Правила | Метод и путь | Право | Запрос | Ответ | Ошибки | Экран | |---|---|---|---|---|---| | `GET /documents/{document_id}/statement` | VIEW | — | 200 `StatementOut` | 404 `NOT_FOUND`, 409 `DOCUMENT_NOT_STATEMENT` | [41] | -| `PATCH /documents/{document_id}/statement` | EDIT | `StatementPatch {header?, values[]}` | 200 `StatementOut` (состояние после правки) | 404, 409 `FILE_SUPERSEDED` / `DOCUMENT_READONLY` / `DOCUMENT_NOT_CONFIRMABLE` / `DOCUMENT_NOT_STATEMENT`, 422 `INVALID_INN` / `UNKNOWN_CELL` / `DUPLICATE_CELL`, 422 (pydantic-массив) | [41-02] «Сохранить» | +| `PATCH /documents/{document_id}/statement` | EDIT | `StatementPatch {header?, values[]}` (`values[]` — `{line_id | line_code, column_id, amount}`) | 200 `StatementOut` (состояние после правки) | 404, 409 `FILE_SUPERSEDED` / `DOCUMENT_READONLY` / `DOCUMENT_NOT_CONFIRMABLE` / `DOCUMENT_NOT_STATEMENT`, 422 `INVALID_INN` / `UNKNOWN_CELL` / `DUPLICATE_CELL`, 422 (pydantic-массив) | [41-02] «Сохранить» | | `POST /documents/{document_id}/statement/checks/accept` | EDIT | `ChecksAcceptIn {comment}` (1–2000 символов после обрезки) | 200 `StatementOut` | 404, 409 `NOTHING_TO_ACCEPT` и те же 409, что у `PATCH`, 422 (pydantic-массив) | [41] блок «Логические ошибки» | | `POST /documents/{document_id}/confirm` | EDIT | — | 200 `DocumentOut` (как в Срезе 2c) | 404, 409 `FILE_SUPERSEDED` / `ALREADY_CONFIRMED` / `DOCUMENT_NOT_CONFIRMABLE` / **`PERIOD_REQUIRED`** / **`CHECKS_NOT_PASSED`** | [41] «Подтвердить данные» | | `POST /documents/{document_id}/comments` | EDIT | `CommentIn {text}` (1–4000 символов после обрезки пробелов) | 201 `CommentOut` | 404, 422 (pydantic-массив) | [41] «Добавить комментарий» | @@ -65,8 +65,8 @@ D-1…D-14, «Decisions made without discussion» №1–№22). Правила `address` (все `| null`). - `columns[]` — `ColumnOut {id, column_index, label | null, period_date | null, is_current | null}`; `is_current = true` у колонки с самой поздней датой. -- `lines[]` — по коду строки (по возрастанию `line_code`): `LineOut {id, line_code | null, line_code_confidence | null, line_name | - null, line_name_confidence | null, values[]}`; `values[]` — по одной записи на **каждую** колонку формы (`ValueOut {id, column_id, +- `lines[]` — по коду строки (по возрастанию `line_code`): `LineOut {id | null, origin: 'source' | 'template', line_code | null, line_code_confidence | null, line_name | + null, line_name_confidence | null, values[]}`; `values[]` — по одной записи на **каждую** колонку формы (`ValueOut {id | null, column_id, column_index, amount | null, string_value | null, confidence | null, is_recognized | null, is_modified, original_amount | null, modified_by | null, modified_at | null}`). `amount` — текущее значение (после правок); `original_amount` заполняется с первой правки; `string_value` — неразобранный текст ячейки (когда `amount` пуст). @@ -74,10 +74,10 @@ D-1…D-14, «Decisions made without discussion» №1–№22). Правила expected, actual}` (`relation` — читаемая запись соотношения, `expected` и `actual` — суммы строками; `column_index = -1` — у межформенной проверки колонки нет); `accepted` — `{by, at, comment}` (в коде схема `ChecksAcceptedOut`) или `null`. -**`StatementPatch`** `{header?: {inn?, report_date?, units?}, values?: [{line_id, column_id, amount}]}` — частичное тело, лишние поля +**`StatementPatch`** `{header?: {inn?, report_date?, units?}, values?: [{line_id | line_code, column_id, amount}]}` — частичное тело, лишние поля запрещены (422). `header.inn` — строка из 10 или 12 цифр (любое другое значение, включая `null`, — 422 `INVALID_INN`: ИНН очистить нельзя); `header.report_date` — дата, `null` очищает; `header.units` — непустая -строка после обрезки пробелов, ≤ 50 (`null` — «не менять», единицы очистить нельзя; пробельная строка — 422 pydantic-массивом). Поле шапки, которого нет в теле, не меняется. `values` — до 5000 ячеек; одна ячейка дважды в запросе — 422 -`DUPLICATE_CELL`; `line_id`/`column_id` не этого документа — 422 `UNKNOWN_CELL`. **`ChecksAcceptIn`** `{comment}`. +строка после обрезки пробелов, ≤ 50 (`null` — «не менять», единицы очистить нельзя; пробельная строка — 422 pydantic-массивом). Поле шапки, которого нет в теле, не меняется. `values` — до 5000 ячеек: `{line_id | line_code, column_id, amount}` (ровно одно из `line_id`/`line_code`); одна ячейка дважды в запросе — 422 +`DUPLICATE_CELL`; `line_id`/`column_id` не этого документа — 422 `UNKNOWN_CELL`. **Полный состав формы (arm-core#80):** в `StatementOut` `lines[].origin` (`source` | `template`), у достроенных по справочнику редакции строк и ячеек `id = null`; `line_code` создаёт строку (`line_code_original_value = NULL`) при первой непустой сумме. Новые случаи `UNKNOWN_CELL`: код уже есть в форме (передайте `line_id`), код вне справочника, нет редакции. **`ChecksAcceptIn`** `{comment}`. **`CommentIn`** `{text}`. **`CommentOut`** `{id, text, author: {id, full_name}, created_at}`. **`UserBriefOut`** `{id, full_name, is_active}`. diff --git a/dev/frontend-api-guide.md b/dev/frontend-api-guide.md index 0a059f7..c642be3 100644 --- a/dev/frontend-api-guide.md +++ b/dev/frontend-api-guide.md @@ -377,13 +377,19 @@ UI прячет элементы по `GET /me → permissions` (объедин | Метод и путь | Право | Запрос | Ответ | Ошибки | |---|---|---|---|---| | `GET /documents/{document_id}/statement` | VIEW | — | `StatementOut` | 409 `DOCUMENT_NOT_STATEMENT` (ОСВ, расшифровка, ББ/ОФР без данных формы) | -| `PATCH /documents/{document_id}/statement` | EDIT | `{header?: {inn?, report_date?, units?}, values?: [{line_id, column_id, amount}]}` (до 5000 ячеек) | `StatementOut` — состояние после правки | 409 `FILE_SUPERSEDED`, `DOCUMENT_READONLY` (уже `CONFIRMED`), `DOCUMENT_NOT_CONFIRMABLE` (статус `ERROR`), `DOCUMENT_NOT_STATEMENT`; 422 `INVALID_INN`, `UNKNOWN_CELL`, `DUPLICATE_CELL`; 422 массивом | +| `PATCH /documents/{document_id}/statement` | EDIT | `{header?: {inn?, report_date?, units?}, values?: [{line_id | line_code, column_id, amount}]}` (до 5000 ячеек; для достроенной строки с `id = null` — `line_code`) | `StatementOut` — состояние после правки | 409 `FILE_SUPERSEDED`, `DOCUMENT_READONLY` (уже `CONFIRMED`), `DOCUMENT_NOT_CONFIRMABLE` (статус `ERROR`), `DOCUMENT_NOT_STATEMENT`; 422 `INVALID_INN`, `UNKNOWN_CELL`, `DUPLICATE_CELL`; 422 массивом | | `POST /documents/{document_id}/statement/checks/accept` | EDIT | `{comment}` (1–2000) | `StatementOut` | 409 `NOTHING_TO_ACCEPT` и те же 409 | Главное (подробно — `api-contract-slice-3b.md` §3–§5): -- `StatementOut = {document, edition, header, columns[], lines[], checks}`. У **каждой** пары «строка × колонка» есть `ValueOut` - со своим `id` — для правки передавайте `line_id` и `column_id` существующей ячейки, создавать значения не нужно. +- `StatementOut = {document, edition, header, columns[], lines[], checks}`. У **каждой** пары «строка × колонка» есть `ValueOut`. + Ядро достраивает полный состав формы редакции (справочник `statement_lines.yaml`, arm-core#80): `lines[].origin` — + `source` (из источника) или `template` (достроена); у достроенных строк и ячеек `id = null`, в БД их нет. Для правки + передавайте `{line_id | line_code, column_id, amount}`: у существующей строки — `line_id`; у достроенной (`id = null`) — + `line_code`, первая непустая сумма создаёт строку и ячейку (`is_modified = true`). Очистка (`amount: null`) возвращает + созданную ячейку в пустую. `UNKNOWN_CELL` — и новые случаи: `line_code` уже есть в форме (передайте `line_id`), код вне + справочника редакции, у документа нет редакции (нет даты периода — строки не достраиваются; текст ошибки тот же, что для кода вне справочника). Пустые достроенные строки + не участвуют в проверках §7 и статусе. - Подсветка: `confidence < 100`, `is_recognized = false`, `is_modified = true` (исходное — `original_amount`). Возврат ячейки к исходному значению снимает `is_modified`. - Правка даты отчёта пересчитывает `document.period_*`, `reporting_date` и `edition`; правка ИНН — `counterparty_check`; @@ -592,7 +598,7 @@ async function download(path: string, fallbackName: string): Promise { | `CHECKS_NOT_PASSED` | 409 | подтверждение формы | `failures[] {column_index, relation, expected, actual}` | показать нарушения, предложить акцепт | | `DOCUMENT_NOT_STATEMENT` | 409 | `…/statement` не по форме ББ/ОФР | — | не открывать экран [41] для такого документа | | `DOCUMENT_READONLY` | 409 | правка/акцепт подтверждённой формы | — | перевести экран в просмотр | -| `UNKNOWN_CELL`, `DUPLICATE_CELL` | 422 | правка формы | — | ошибка клиента (перечитать форму) | +| `UNKNOWN_CELL`, `DUPLICATE_CELL` | 422 | правка формы | — | ошибка клиента (перечитать форму); `UNKNOWN_CELL` также: `line_code` уже есть в форме (слать `line_id`), код вне справочника редакции, нет редакции | | `NOTHING_TO_ACCEPT` | 409 | акцепт без нарушений | — | перечитать форму | | `FILE_HAS_CONFIRMED_DOCUMENTS`, `FILE_SUPERSEDES_OTHERS` | 409 | удаление файла | — | скрывать «Удалить» у таких файлов (есть документ, подтверждённый пользователем, или автоподтверждённый документ в версии вне корректировки данных (не `INITIATED`/`DOCUMENTS_PROCESSING`/`DATA_CORRECTION`), см. §3.7; или на файл ссылаются `superseded_by_file_id`) | | `FILE_CONTENT_NOT_FOUND` | 404 | первичка | — | «Файл больше недоступен» | diff --git a/dev/qa-guide.md b/dev/qa-guide.md index 0800209..96bc4c3 100644 --- a/dev/qa-guide.md +++ b/dev/qa-guide.md @@ -540,6 +540,8 @@ curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m js | F6 | ka_go | снова `PATCH` той же ячейки другой суммой | 200; `checks.accepted = null` (акцепт сброшен правкой) | | 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`; данные не изменились | +| F8a | ka_go | `PATCH` `{"values":[{"line_code":"1600","column_id":"<реальный column_id из F2>","amount":"1"}]}` (строка 1600 в форме есть; `column_id` должен быть настоящей колонкой формы, иначе сработает проверка колонок с другим текстом); затем то же с `"line_code":"9999"` (вне справочника) | 422 `UNKNOWN_CELL` в обоих случаях: для 1600 `detail.message` — «строка уже есть в форме — передайте line_id», для 9999 — «строки с таким кодом нет в форме этой редакции»; данные не изменились | +| F8b | ka_go | Новая строка: в API нет операции удаления строки формы — проверять на документе без 1700 (файл «РБ 1 кв. 2025» из прогона q1-2025-run, OQ-156; не фикстура Межениновской) или вручную: `GET …/statement` — строка 1700 есть, `origin = template`, `id = null`; `PATCH` `line_code = "1700"` с суммой | 200; строка получила `id`, `origin = template` (в БД `line_code_original_value IS NULL`; в `StatementOut` это поле не отдаётся), проверки пересчитаны; очистка (`amount: null`) — строка снова пустая, нарушений по ней нет | | 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` для формы в `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` пуст | @@ -792,6 +794,8 @@ PDF из `fixtures/ocr` — отчётность других компаний ( | W26 | В шапке «Дата отчёта» ввести `2025-09-15` → «✓ Сохранить» → «✓ Подтвердить данные» | красный блок «<текст сервера>. Исправьте дату отчёта в шапке и сохраните.» (409 `PERIOD_REQUIRED`: дата не на конец квартала, F9). Дата вводится строго `ГГГГ-ММ-ДД` | | W27 | Дата `2025-06-30` → «✓ Сохранить» → «✓ Подтвердить данные» | уведомление «Документ подтверждён», статус «Загружен», «Редактировать» и «Подтвердить данные» неактивны | | W28 | Открыть «ОФР» того же файла [41-02] | заголовок «Отчёт о финансовых результатах», колонки периодов ОФР; остальное — как W20 | +| W28a | В «Бух. балансе» посмотреть строки 1600 и 1700 | строки выделены фиолетовым фоном и жирным шрифтом | +| W28b | «✎ Редактировать» на форме, где есть достроенные (в источнике отсутствующие) строки | пустые достроенные строки видны, редактируются и не подсвечены как ошибки; ввод суммы → «✓ Сохранить» создаёт строку, проверки пересчитываются | Формы из Excel (`fixtures/statements-xlsx`, раздел 6.9) открываются тем же экраном, но слева вместо скана — «Превью недоступно для этого формата» и кнопка «Скачать исходник». Документы «Прочий документ» (расшифровки) в панели не