26 KiB
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 (в том числе consolidation_id: uuid | null,
v5.18 — консолидация, в контексте которой файл загружен), текущее задание (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
Scenario: Консолидация в карточке
- WHEN один файл ЮЛ загружен в консолидацию
C, другой — без контекста - THEN в списке ЮЛ и в
GET /files/{id}у первогоconsolidation_id = C, у второгоconsolidation_id = null
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 без родителя, у строк уровня 2parent_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, origin, 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.
Форма SHALL достраиваться в ответе по справочнику строк редакции edition (api/rules/statement_lines.yaml, свой
список кодов и наименований для BALANCE и INCOME каждой редакции): строки справочника, кода которых нет в форме,
добавляются с id = null, origin = "template", наименованием из справочника и *_confidence = null; у каждой строки
SHALL быть значение по каждой колонке формы — недостающее добавляется с id = null, amount = null,
string_value = null, confidence = null, is_recognized = null, is_modified = false, original_amount = null,
modified_by = null, modified_at = null. При edition = null строки не достраиваются, недостающие значения —
достраиваются. Строка из БД имеет origin = "source", если пришла из источника, и "template", если создана правкой
аналитика (line_code_original_value IS NULL). Строки источника, которых нет в справочнике, отдаются как есть.
Достроенные строки и значения MUST NOT записываться в БД при чтении.
Scenario: Форма для экрана
- WHEN запрошена форма
BALANCEизmezheninovskaya/2025-Q3/1_Форма1_бух_09 - THEN
edition = fsbu4, все 37 строк файла —origin = sourceсid, каждый код справочникаfsbu4/BALANCEесть средиlines, у каждой строки значения по всем колонкам,checks.failedпуст
Scenario: Строка, потерянная источником
- WHEN у формы
BALANCEза 2025 Q1 в БД нет строки 1700 (OCR склеил «1500\n1700» до OQ-156) - THEN в
linesесть 1700:id = null,origin = template,line_name = "БАЛАНС", значения по всем колонкамid = null,amount = null,is_recognized = null; статус документа иchecks— те же, что без достройки (1600 = 1700нарушено: 1700 отсутствует и считается 0)
Scenario: Недостающая ячейка
- WHEN у строки 1150 в БД есть значение только для колонки 0, а у формы две колонки
- THEN у 1150 два значения; значение колонки 1 —
id = null,amount = null
Scenario: Нет даты периода
- WHEN у формы OCR не определена дата периода (
edition = null) - THEN
lines— только строки из БД, у каждой значения по всем колонкам
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 | line_code, плюс 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».
Ячейка, которой нет в БД, SHALL создаваться при правке:
- по
line_idстроки формы иcolumn_idколонки формы, если у строки нет значения в этой колонке; - по
line_code, если строки с этим кодом в форме нет, а код есть в справочнике редакции документа для типа формы: создаётсяfinancial_statement_line(line_code,line_nameиз справочника,line_code_original_value = NULL,line_name_original_value = NULL,*_confidence = NULL,*_modified = false); несколько ячеек одной новой строки в запросе создают одну строку. Созданная ячейка —original_amount = NULL,is_recognized = NULL,confidence = NULL,string_value = NULL, далее общая логика правки.line_code, строка с которым в форме уже есть, код вне справочника редакции или редакция не определена — 422UNKNOWN_CELL; колонка не этой формы — 422UNKNOWN_CELL; одна ячейка дважды в запросе — 422DUPLICATE_CELL.amount = nullдля ячейки, которой нет в БД, — не изменение: ничего не создаётся и не аудируется.
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 у формы
BALANCEредакцииfsbu4нет строки 1700, аналитик передаёт{line_code: "1700", column_id: <колонка 0>, amount: 2371333} - THEN в БД создана строка 1700 (
line_code_original_value = NULL,line_name = "БАЛАНС") и значениеamount = 2371333,original_amount = NULL,is_modified = true; в ответе у 1700idзаполнен,origin = template; аудитRECOGNIZED_DATA_EDITсbefore.amount = null; акцепт сброшен, статус пересчитан
Scenario: Код, который уже есть в форме
- WHEN передан
line_code: "1150", а строка 1150 в форме есть - THEN 422
UNKNOWN_CELL, данные не изменились
Scenario: Код вне справочника
- WHEN передан
line_code: "1105"для формы редакции66n - THEN 422
UNKNOWN_CELL
Scenario: Пустая сумма в несуществующую ячейку
- WHEN передан
{line_code: "1530", column_id: <колонка 0>, amount: null}и строки 1530 нет - THEN 200, строка не создана, аудита нет
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-кодированное исходное имя
Requirement: Рабочий стол консолидации для ЮЛ
GET /api/v1/consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files (DOCUMENT_VIEW; limit,
offset, include_superseded=false по умолчанию; §9.5 п. 11 v5.18) SHALL отвечать 404 NOT_FOUND на консолидацию вне
видимости или несуществующую («консолидация не найдена») и на ЮЛ вне видимости, иначе возвращать Page[FileOut] —
объединение файлов ЮЛ, у которых stored_file.consolidation_id = consolidation_id, и файлов ЮЛ, у которых есть документ
с записью consolidation_document к любой версии этой консолидации; порядок и карточка — как у списка файлов ЮЛ;
SUPERSEDED — только при include_superseded=true (в обоих множествах). Принадлежность ЮЛ к ГК консолидации не
проверяется: чужому ЮЛ соответствует пустой список.
Scenario: Стол показывает только файлы консоли
- WHEN у ЮЛ три файла: загружен в консолидацию 2025 Q1, загружен в консолидацию 2025 Q2, без контекста
- THEN стол 2025 Q1 содержит только первый; стол 2025 Q2 — только второй;
GET /legal-entities/{id}/files— все три
Scenario: Подхваченный файл виден через привязку
- WHEN файл с
CONFIRMEDОСВ за 2025 Q1 загружен без контекста и его документ привязан к версии консолидации 2025 Q2 (подхват при создании версии) - THEN файл есть в столе 2025 Q2, хотя
consolidation_id IS NULL
Scenario: Заменённый файл скрыт по умолчанию
- WHEN файл консолидации заменён (
SUPERSEDED) новым файлом той же консолидации - THEN по умолчанию в столе только новый; с
include_superseded=true— оба
Scenario: Чужая консолидация
- WHEN
ka_rfзапрашивает стол консолидации ГК другого филиала и несуществующий id - THEN оба ответа 404 с одинаковым телом