Andrey Epifancev 89abf0a8ec openspec: архив change slice-3b-statements-api (2026-10-07) — спеки documents-api, osv-result-mapping, statement-checks и др.
Ссылки README и CLAUDE.md переведены на архив.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-07 15:23:35 +04:00

18 KiB
Raw Blame History

documents-api Specification

Purpose

Чтение файлов, документов и строк ОСВ для панели «Первичные документы» (подсрез 2d) и подтверждение документа.

Requirements

Requirement: Файлы организации и карточка файла

GET /api/v1/legal-entities/{id}/files (DOCUMENT_VIEW, policy; include_superseded=false по умолчанию) SHALL возвращать Page файлов ЮЛ по убыванию uploaded_at: поля stored_file, текущее задание (status, error_code, error_message, mapping_error (metrics.mapping_error: причина ERROR файла после неудачного маппинга, когда у задания error_code пуст), metrics.warnings, metrics.mapping_warnings, finished_at), документы файла (id, document_type, osv_account_code, period_year, period_quarter, is_cumulative, status, period_check, counterparty_check, comments_count). GET /api/v1/files/{file_id} SHALL отдавать ту же карточку; чужой или несуществующий файл — 404.

Scenario: Список после обработки

  • WHEN у ЮЛ два файла: PROCESSED с документом и ERROR
  • THEN оба в списке; у первого documents непустой, у второго job.error_code заполнен

Scenario: Счётчик комментариев

  • WHEN к документу файла добавлены два комментария
  • THEN в карточке файла у документа comments_count = 2

Requirement: Документ и строки

GET /api/v1/documents/{document_id} (DOCUMENT_VIEW, policy по ЮЛ владельца) SHALL отдавать документ, статус, проверки, locator, схему группировок листа из recognition_job.group_schema и file_id. GET /api/v1/documents/{document_id}/entries SHALL отдавать Page строк по возрастанию row_number (limit ≤ 2000, по умолчанию 500) с полями osv_entry, включая parent_entry_id, account_id, counterparty_id.

Scenario: Строки деревом

  • WHEN запрошены строки документа ОСВ 66
  • THEN первая строка — счёт 66 уровня 0 без родителя, у строк уровня 2 parent_entry_id указывает на субсчёт

Requirement: Подтверждение документа

POST /api/v1/documents/{document_id}/confirm (DOCUMENT_EDIT, policy) SHALL переводить документ из RECOGNIZED или NEEDS_CHECK в CONFIRMED (confirmed_by, confirmed_at) с audit_event(DOCUMENT_CONFIRM, PRIMARY_DOCUMENTS); из CONFIRMED повторно — 409 ALREADY_CONFIRMED; документ файла SUPERSEDED — 409 FILE_SUPERSEDED; документ в любом другом статусе (например, ERROR) — 409 DOCUMENT_NOT_CONFIRMABLE. Для BALANCE/INCOME дополнительно: пустые period_year или period_quarter — 409 PERIOD_REQUIRED; непройденные неакцептованные жёсткие проверки §7 — 409 CHECKS_NOT_PASSED со списком нарушений в detail.failures; при подтверждении SHALL записываться financial_statement_meta.reviewed_by/at. Несовпадение контрагента и уверенность значений подтверждение MUST NOT блокировать. Если совпали несколько условий, порядок: FILE_SUPERSEDED, ALREADY_CONFIRMED, DOCUMENT_NOT_CONFIRMABLE, PERIOD_REQUIRED, CHECKS_NOT_PASSED. Подтверждение берёт блокировки в порядке «файл → документ → (advisory-блокировка привязок ГК) → версии консолидации (по возрастанию id)»: файл — FOR UPDATE, документ — FOR NO KEY UPDATE (R-3; совместима со вставкой привязки FOR KEY SHARE), версии — FOR UPDATE; состояние перечитывается под этими блокировками; документ или файл, исчезнувшие за время ожидания блокировки, — 404 NOT_FOUND. В той же транзакции документ SHALL привязываться (consolidation_document) ко всем версиям «в работе» (INITIATED, DOCUMENTS_PROCESSING, DATA_CORRECTION), которым он подходит: ЮЛ документа ∈ consolidation_member версии, period_year = год версии, period_quarter не NULL и (is_cumulative true или NULL → period_quarter = квартал версии; false → period_quarter ≤ квартала версии). ОСВ за Q1 равнозначна нарастающей и квартальной (R-14): она подходит любой версии года при любом is_cumulative; ББ/ОФР за Q1 — отчёт на конец квартала и подходят только версии за Q1 (R-14a). Привязанный ранее документ того же ЮЛ с тем же ключом (document_type, osv_account_code, period_quarter, признак нарастающего: is_cumulative true или NULL; для ОСВ за Q1 признак не различается) в той же версии SHALL отвязываться (R-1). Каждая версия, получившая привязку, из INITIATED или DOCUMENTS_PROCESSING SHALL переходить в DATA_CORRECTION (changed_by = подтвердивший). Блокировка версии (consolidation_lock) для подтверждения MUST NOT требоваться.

Scenario: Причина ошибки маппинга в карточке файла

  • WHEN файл в ERROR после неудачного маппинга (задание done, error_code пуст)
  • THEN job.mapping_error содержит причину, job.error_code пуст

Scenario: Документ не подтверждается

  • WHEN подтверждается документ в статусе ERROR
  • THEN 409 DOCUMENT_NOT_CONFIRMABLE, статус не изменился

Scenario: Подтверждение

  • WHEN ka_rf подтверждает документ NEEDS_CHECK своего филиала
  • THEN status = CONFIRMED, confirmed_by = пользователь, audit_event(DOCUMENT_CONFIRM) с before_value.status = NEEDS_CHECK

Scenario: Привязка к версии и переход статуса

  • WHEN версия ГК 2024 Q3 в INITIATED с ЮЛ в периметре; подтверждается квартальная ОСВ 60 этого ЮЛ за 2024 Q2
  • THEN consolidation_document(version, document) создан; версия в DATA_CORRECTION; запись истории INITIATED → DATA_CORRECTION с changed_by = пользователь

Scenario: Не подходит по периоду

  • WHEN подтверждается нарастающая ОСВ 60 за 2024 Q2 при версии за 2024 Q3
  • THEN привязки нет, статус версии не изменился

Scenario: Версия в CANCELLED

  • WHEN подходящий документ подтверждается при версии CANCELLED
  • THEN привязки нет

Scenario: Форма с нарушением проверок

  • WHEN подтверждается BALANCE с непройденным жёстким соотношением без акцепта
  • THEN 409 CHECKS_NOT_PASSED, в detail.failures — нарушение; после акцепта подтверждение проходит

Scenario: Форма без периода

  • WHEN подтверждается форма из OCR, у которой нет ни даты отчёта, ни дат колонок
  • THEN 409 PERIOD_REQUIRED; после правки даты отчёта подтверждение проходит

Scenario: Форма в чек-листе

  • WHEN версия ГК 2025 Q3 INITIATED, ЮЛ в периметре; подтверждается его BALANCE за 2025 Q3
  • THEN привязка создана, ячейка «Бух. баланс» чек-листа ЮЛ — CONFIRMED, reviewed_by шапки = пользователь

Requirement: Подхват документов при создании версии и расширении периметра

При создании версии и при добавлении ЮЛ в периметр ядро SHALL привязать уже подтверждённые документы этого ЮЛ из незаменённых файлов, подходящие версии (условия — как при подтверждении); на один ключ (document_type, osv_account_code, period_quarter, признак «нарастающий»; NULL признака — нарастающий; для ОСВ за Q1 признак не различается, R-1, R-14) — документ с самым поздним confirmed_at. Удаление ЮЛ из периметра SHALL снимать привязку всех его документов в этой версии. Подхват и отвязка статус версии MUST NOT менять.

Scenario: Фикстуры, подтверждённые до консолидации

  • WHEN ОСВ 60 РБ за Q1, Q2, Q3 2024 подтверждены, затем создана консолидация ГК за 2024 Q3
  • THEN три consolidation_document на эти документы; в чек-листе ячейка ОСВ 60 РБ — CONFIRMED; версия в INITIATED

Scenario: Две подтверждённые версии одного документа

  • WHEN у ЮЛ два незаменённых файла с CONFIRMED ОСВ 62 за 2024 Q3 (разные locator), создана версия 2024 Q3
  • THEN привязан документ с более поздним confirmed_at

Requirement: Форма ББ/ОФР для экрана проверки

GET /api/v1/documents/{document_id}/statement (DOCUMENT_VIEW, policy по ЮЛ) SHALL отдавать для документа BALANCE/INCOME: document (id, document_type, status, period_year, period_quarter, reporting_date, counterparty_check, locator, file_id); edition (66n при дате периода ≤ 2024-12-31, fsbu4 — позже, null без даты); header — для inn, report_date, units: value, original_value, confidence (у ИНН), verified, modified; плюс organization_name, okud, okpo, okopf, okfs, address; columns[] (id, column_index, label, period_date, is_current); lines[] по line_code (id, line_code, line_code_confidence, line_name, line_name_confidence, values[] по column_index: id, column_id, amount, string_value, confidence, is_recognized, is_modified, original_amount, modified_by, modified_at); checks (failed, soft_failed, cross_form, accepted: by, at, comment или null). Документ другого типа — 409 DOCUMENT_NOT_STATEMENT; чужой или несуществующий — 404.

Scenario: Форма для экрана

  • WHEN запрошена форма BALANCE из mezheninovskaya/2025-Q3/1_Форма1_бух_09
  • THEN edition = fsbu4, 37 строк по коду, у каждой строки значения по всем колонкам, checks.failed пуст

Scenario: Не форма

  • WHEN запрошена форма документа OSV
  • THEN 409 DOCUMENT_NOT_STATEMENT

Requirement: Правка формы

PATCH /api/v1/documents/{document_id}/statement (DOCUMENT_EDIT, policy) SHALL принимать частичное тело header (inn — 10 или 12 цифр, иначе 422 INVALID_INN; report_date — дата; units — непустая строка ≤ 50) и values[] (line_id, column_id, amount — число с ≤ 2 знаками или null) и в одной транзакции: при первой правке значения записывать original_amount = прежняя сумма; ставить amount, is_modified = (amount ≠ original_amount), modified_by/at; для поля шапки — *_modified = true, *_original_value не менять; после inn — пересчитать counterparty_check; после report_date — пересчитать period_year, period_quarter, reporting_date; сбросить акцепт проверок; пересчитать статус; записать одно audit_event(RECOGNIZED_DATA_EDIT, RECOGNITION) с before/after изменённых полей и ячеек; вернуть форму, как GET. Документ CONFIRMED — 409 DOCUMENT_READONLY; файл SUPERSEDED — 409 FILE_SUPERSEDED; не форма — 409 DOCUMENT_NOT_STATEMENT; ячейка не этого документа — 422 UNKNOWN_CELL. Запрос без изменений — 200 без аудита. Блокировки — «файл FOR UPDATE → документ FOR NO KEY UPDATE».

Scenario: Правка значения

  • WHEN аналитик меняет сумму строки 1150 колонки 0 с 100 на 120
  • THEN amount = 120, original_amount = 100, is_modified = true, аудит RECOGNIZED_DATA_EDIT с before 100, after 120

Scenario: Возврат к исходному

  • WHEN затем та же ячейка меняется обратно на 100
  • THEN is_modified = false, original_amount = 100

Scenario: Правка даты отчёта

  • WHEN у формы OCR без периода дата отчёта правится на 2025-06-30
  • THEN period_year = 2025, period_quarter = Q2, report_date_modified = true, report_date_original_value не изменился

Scenario: Подтверждённая форма

  • WHEN правится форма CONFIRMED
  • THEN 409 DOCUMENT_READONLY, данные не изменились

Requirement: Акцепт проверок формы

POST /api/v1/documents/{document_id}/statement/checks/accept (DOCUMENT_EDIT, policy) с {comment} (1–2000 символов) SHALL выполнять акцепт (statement-checks), пересчитывать статус и писать audit_event(RECOGNIZED_DATA_EDIT, RECOGNITION) с before/after полей акцепта; без непройденных жёстких проверок — 409 NOTHING_TO_ACCEPT; те же 409, что у правки формы.

Scenario: Акцепт

  • WHEN у формы нарушено 1600 = 1100 + 1200, аналитик акцептует с комментарием «округление в форме клиента»
  • THEN checks.accepted заполнен, документ RECOGNIZED, если других причин NEEDS_CHECK нет

Requirement: Комментарии к документу

POST /api/v1/documents/{document_id}/comments (DOCUMENT_EDIT, policy) с {text} (1–4000 символов после обрезки пробелов) SHALL создавать comment(object_type = 'document') с автором и временем; GET …/comments (DOCUMENT_VIEW) SHALL отдавать комментарии по created_at: {id, text, author: {id, full_name}, created_at}. Правки и удаления нет; комментировать можно документ в любом статусе, в том числе файла SUPERSEDED.

Scenario: Комментарий

  • WHEN аналитик добавляет комментарий «нет подписи руководителя» к подтверждённому документу
  • THEN 201, комментарий в списке с ФИО автора, comments_count документа = 1

Requirement: Исходный файл

GET /api/v1/files/{file_id}/content (DOCUMENT_VIEW, policy) SHALL отдавать содержимое исходного объекта файла из S3 потоком, без чтения целиком в память: Content-Type по file_type, Content-Disposition (attachment по умолчанию, inline при ?disposition=inline) с ASCII-именем и filename* RFC 5987 исходного имени; работает и для файла SUPERSEDED. Объект не найден в S3 — 404 FILE_CONTENT_NOT_FOUND; чужой файл — 404.

Scenario: PDF для просмотра

  • WHEN запрошено содержимое загруженного aep_6.pdf с disposition=inline
  • THEN 200, Content-Type: application/pdf, Content-Disposition: inline; filename="file.pdf"; filename*=UTF-8''aep_6.pdf, тело байт в байт равно загруженному

Scenario: Кириллица в имени

  • WHEN запрошен файл ОСВ_66 за 2024.xls
  • THEN filename* содержит percent-кодированное исходное имя