70 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; строки unreadable / missing с непустой суммой или текстом значения — 409 LINES_UNRESOLVED (detail.lines[] {line_id, row_index, line_code_text}); вторая подтверждённая форма того же типа и периода в файле — 409 DUPLICATE_FORM (detail.document_id); записи document_issue вида relation без акцепта — 409
CHECKS_NOT_PASSED со списком нарушений в detail.failures; при подтверждении SHALL записываться
financial_statement_meta.reviewed_by/at. Несовпадение контрагента и уверенность значений подтверждение MUST NOT
блокировать. Если совпали несколько условий, порядок: FILE_SUPERSEDED, ALREADY_CONFIRMED,
DOCUMENT_NOT_CONFIRMABLE, PERIOD_REQUIRED, LINES_UNRESOLVED, DUPLICATE_FORM, 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шапки = пользователь
Scenario: Неразобранная строка с суммой
- WHEN у формы строка
missingс суммой и одновременно жёсткое нарушение - THEN 409
LINES_UNRESOLVEDс этой строкой вdetail.lines
Scenario: Неразобранная строка только с текстом
- WHEN у формы строка
unreadableбез сумм, но с мусором211(1 |в ячейке значения (string_value) - THEN 409
LINES_UNRESOLVED— потерянная сумма тоже требует разбора
Scenario: Вторая форма того же периода
- WHEN в файле два ББ на 30.09.2025, первый подтверждён, аналитик подтверждает второй
- THEN 409
DUPLICATE_FORMсо ссылкой на первый
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[] в порядке, описанном ниже (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). Элемент нарушения в checks — {column_index, relation, expected, actual, codes, line_ids}; expected и actual SHALL быть null, когда сторона соотношения пуста
(пустая цель при непустых членах — expected = null, пакет arm_statements, P1-21), см. требование «Блок checks
ответа формы из document_issue». Документ другого типа — 409 DOCUMENT_NOT_STATEMENT; чужой или несуществующий — 404.
Состав ответа v2.0. StatementOut SHALL отдавать: у документа form_variant; у шапки needs_review_fields, source (header_source;
confidence — строкой Decimal, как прочие уверенности API), organization_name_modified и okei; у колонок period_kind, label_confidence; у строк id, origin (template ⇔
row_index = null), row_index, source_ref, line_status, line_code, line_code_text, line_code_confidence,
line_name (из файла), reference_name (справочное имя по коду — первое имя кода в словаре — или null),
parent_row_index, parent_code, needs_review, is_confirmed, line_code_modified; у значений text
(source_text), needs_review, is_confirmed и прежние поля; issues[] — записи document_issue с line_ids,
column_id, column_index, field, codes; checks — нарушения из document_issue с codes и line_ids;
edition_lines[] {code, name} — справочник строк для подсказки кода: коды словаря имён формы в редакции ответа по одному
(имя — первое), у формы без редакции — коды всех редакций по одному. DocumentBrief (FileOut.documents[]) и
DocumentOut SHALL отдавать issue_counts {error, info} — число записей document_issue документа по уровню. Порядок
lines — по возрастанию кода, как сейчас (schemas/statements.py), одинаковые коды — по row_index; строки
unreadable / missing — после ближайшей предыдущей по row_index строки, размещённой по коду, без такой — в начале;
detail — после родителя. Разделы и размещение итогов на экране («как на бумаге», Р-14) строит экран.
Форма 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 = "template" ⇔ row_index IS NULL (создана правкой аналитика), иначе "source". Строки источника, которых нет в справочнике, отдаются как есть.
Достроенные строки и значения MUST NOT записываться в БД при чтении и MUST NOT участвовать в проверках §7.
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.failedсодержит соотношения с пустой целью 1700 (1700 = 1300 + 1400 + 1500,expected = null,actual— сумма членов) и1600 = 1700(цель 1600 заполнена, 1700 пуста) — такие же, как без достройки; подтверждение формы требует правки строки 1700 или акцепта проверок
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
Scenario: Документ, принятый по 1.x
- WHEN форма загружена до перехода сервиса (результат 1.2)
- THEN у строк
line_status = ok,row_indexпо порядку,source_ref = null,origin = source, у значенийtext = string_value,issuesсодержат нарушения §7, посчитанные ядром, иMAPPING_WARNING; экран работает
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».
Расширения v2.0. PATCH /documents/{id}/statement SHALL принимать помимо header {inn, report_date, units} и values (сумма):
header.organization_name— непустая строка до 1000 символов:organization_name_modified = true,organization_name_original_valueне меняется, пересчитываетсяcounterparty_check, аудитbefore/after;header.report_date, равный текущему, у поля изneeds_review_fields— подтверждение даты: поле уходит изneeds_review_fields, снимаетсяREPORT_DATE_UNCERTAIN, аудит; иначе запрос без изменений — 200 без эффектов, как прежде;values[] {line_id, column_id, confirmed: true | false}— подтвердить ячейку без изменения значения или снять подтверждение;columns[] {column_id, period_date}— дата колонке (обязательная дата;null— 422 ошибка валидации; пересчитываетis_currentи период документа по правилу §9.5 п. 2); ручная дата ставит колонкеperiod_kind = absolute, чтобы следующая правка даты отчёта её не перезаписала; дата, равная текущей, у колонки сCOLUMN_DATE_UNCERTAINилиCOLUMN_WITHOUT_DATE— подтверждение: предупреждение снимается,period_kind = absolute, аудит (иначе запрос без изменений — 200 без эффектов); правкаheader.report_date(в том числеnull) пересчитываетperiod_dateколонок сperiod_kind = relative:*общим кодом (nullдаётnull);lines[] {line_id, line_code}— назначить или сменить код;lines[] {line_id, line_status: "detail"}— отметить расшифровкой;lines[] {line_id, confirmed: true | false}— подтвердить код или снять подтверждение.
Переходы: unreadable / missing / detail → ok назначением кода; unreadable / missing / ok → detail
отметкой; ok → ok сменой кода; ok с неуверенным кодом — подтверждением. Обратного перехода detail →
unreadable / missing нет: ошибочная отметка исправляется назначением кода, исходное состояние — перезагрузкой файла.
line_status принимает только "detail"; "ok" — 422 LINE_CODE_REQUIRED (в ok строку переводит только
line_code). Назначенный код: line_code_confidence = 100, line_code_modified = true, needs_review строки =
false. Подтверждённый код (confirmed: true): is_confirmed = true, needs_review строки = false,
line_code_confidence остаётся как пришло, line_code_modified не ставится; снятие подтверждения (confirmed: false)
возвращает needs_review по прежнему правилу (Р-34). line_code_original_value не меняется; после любого изменения статусов или кодов
parent_row_index всех строк detail документа пересчитывается как ближайшая строка ok выше по row_index
(требование «Пересчёт после правки»). Код — 4–5 цифр (иначе 422 INVALID_LINE_CODE), может совпадать с
существующим; незнакомый словарю — info. Строка с тем же кодом, созданная правкой (row_index IS NULL), с суммами —
409 LINE_CODE_TAKEN (сначала очистить её суммы или выбрать другой код); без сумм — удаляется (её id убирается из
document_issue.line_ids), её место занимает назначенная строка. Строки источника с тем же кодом не трогаются. В одном
элементе больше одного действия или ни одного — 422 INVALID_LINE_PATCH; строка не этого документа — 422
UNKNOWN_LINE; колонка не этого документа — 422 UNKNOWN_COLUMN. Ошибки элемента lines[] (LINE_CODE_TAKEN,
LINE_CODE_REQUIRED, INVALID_LINE_CODE, INVALID_LINE_PATCH, UNKNOWN_LINE) SHALL нести в detail line_id и
line_code первого неверного элемента (line_code — код элемента или null). Правка суммы по line_code (для достроенной строки)
при наличии строки ok с этим кодом — 422 UNKNOWN_CELL (передайте line_id). Удаления строк источника нет. Строки,
созданные правкой: row_index = null, source_ref = null, line_status = ok, line_code_original_value = null. Аудит
RECOGNIZED_DATA_EDIT с before/after по коду, статусу, родителю, подтверждениям и наименованию организации.
Ограничения состояния — как прежде (DOCUMENT_READONLY, FILE_SUPERSEDED, DOCUMENT_NOT_STATEMENT).
Части запроса применяются в порядке lines → values → columns → header; вся адресация (422) проверяется до первой записи. Один line_id дважды в lines — 422 INVALID_LINE_PATCH; UNKNOWN_CELL по line_code — по кодам строк ok с учётом назначений lines этого запроса.
Ячейка, которой нет в БД, SHALL создаваться при правке:
- по
line_idстроки формы иcolumn_idколонки формы, если у строки нет значения в этой колонке; - по
line_code, если строки с этим кодом в форме нет, а код есть в справочнике редакции документа для типа формы: создаётсяfinancial_statement_line(line_code,line_nameиз справочника,line_code_original_value = NULL,row_index = NULL,source_ref = NULL,line_status = ok,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, данные не изменились
Scenario: Назначение кода строке-мусору (AURORA-1462)
- WHEN аналитик назначает строке
unreadable«ИЧ,250» (row_index = 2) код 1250 - THEN строка
ok,line_code = 1250,line_code_confidence = 100,line_code_modified = true,line_code_original_valueостаётсяnull,row_indexостаётся 2 (origin = source); §7 и статус пересчитаны
Scenario: Отметка расшифровкой
- WHEN аналитик отмечает строку
missingс суммой как расшифровку - THEN
line_status = detail, строка выходит из сумм и не блокирует подтверждение
Scenario: Код занят шаблонной строкой с суммами
- WHEN аналитик назначает строке
unreadableкод 1250, а строка 1250 создана правкой аналитика (row_index = null) и имеет сумму - THEN 409
LINE_CODE_TAKEN, ничего не изменено
Scenario: Код занят пустой шаблонной строкой
- WHEN аналитик назначает строке
unreadableкод 1250, а строка 1250 создана правкой и сумм не имеет - THEN шаблонная строка удалена, её
idубран изdocument_issue.line_ids; строка источника сталаokс кодом 1250
Scenario: Правка наименования организации
- WHEN у формы
NO_ORGANIZATION_NO_INN, аналитик вводитheader.organization_name - THEN наименование сохранено,
organization_name_modified = true, исходное — вorganization_name_original_value,counterparty_checkпересчитан, предупреждение снято, аудит
Scenario: Подтверждение неуверенной даты отчёта
- WHEN
report_dateвneeds_review_fields, аналитик отправляет PATCH с тем жеheader.report_date - THEN
needs_review_fieldsпуст,REPORT_DATE_UNCERTAINснят, статус пересчитан, аудит
Scenario: Ручная дата относительной колонки
- WHEN колонке
relative:prev_year_endаналитик ставитperiod_date31.12.2024, затем правит дату отчёта - THEN после первой правки
period_kind = absolute; правка даты отчёта её дату не меняет
Scenario: Подтверждение неуверенной даты колонки
- WHEN у колонки
COLUMN_DATE_UNCERTAIN, аналитик отправляетcolumns: [{column_id, period_date}]с той же датой - THEN предупреждение снято,
period_kind = absolute, статус пересчитан, аудит
Scenario: Статус ok без кода
- WHEN приходит
lines: [{line_id, line_status: "ok"}] - THEN 422
LINE_CODE_REQUIRED
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 с одинаковым телом
Requirement: Предупреждения и нарушения документа — document_issue
Ядро SHALL хранить предупреждения и нарушения документа в document_issue: вид (warning | relation | soft), код,
уровень (error | info), сообщение, список line_id, column_id (колонка формы) или column_index (столбец
предупреждения, которого нет среди колонок формы), поле шапки, источник (service | core); для нарушений — codes
(коды цели и членов), текст соотношения, expected, actual. Нарушение из checks.relations_failed — kind = relation, code = RELATION_FAILED, severity = error; из soft_relations_failed — kind = soft, code = RELATION_SOFT, severity = info; line_ids — все строки ok кодов codes в документе. При загрузке пишутся
предупреждения и checks результата (источник service) и проверки ядра уровня info (источник core):
CODE_NOT_IN_DICTIONARY (кода нет в словаре имён редакции документа) и CODE_NOT_IN_EDITION (код не из перечня
редакции документа и его варианта — по копии перечня arm_statements) — только для четырёхзначных кодов, одна запись на код документа (Р-40), NAME_MISMATCH, RESULT_STATUS_INCONSISTENT.
Редакция документа для этих проверок — edition точки входа пакета для пересчёта (statement-normalization):
редакция с лучшей подпоследовательностью кодов строк ok документа, при равенстве — по эффективной дате; так бланк 66н,
сданный в 2025 году, проверяется по 66n, а не по дате. Поле edition ответа GET …/statement и выбор шаблонных
строк по-прежнему — по дате периода (§7 «Полный состав формы»). document_issue ведётся у любого документа файла: у
ОСВ — предупреждения osvDocument.warnings результата 2.0 (вид warning, источник service, OSV_*, info). При перезагрузке файла записи документа
удаляются вместе с ним. При пересчёте после правки записи relation и soft заменяются целиком независимо от источника;
записи warning источника service сохраняются, кроме снимаемых правкой: правка header.report_date (в том числе
подтверждение тем же значением) снимает PERIOD_UNKNOWN, REPORT_DATE_UNPARSED, REPORT_DATE_UNCERTAIN; правка
header.inn или header.organization_name — NO_ORGANIZATION_NO_INN; правка columns[].period_date —
COLUMN_WITHOUT_DATE и COLUMN_DATE_UNCERTAIN этой колонки; прочие error сервиса снимаются только перезагрузкой
файла. При удалении шаблонной строки её id SHALL убираться из line_ids всех записей документа. GET …/statement и
POST …/confirm SHALL читать нарушения §7 только из document_issue.
Scenario: Дата колонке
- WHEN у колонки предупреждение
COLUMN_WITHOUT_DATE, аналитик ставит ейperiod_dateчерез PATCH - THEN предупреждение удаляется, статус пересчитывается
Scenario: Неснимаемое предупреждение
- WHEN у документа
SEGMENT_COLUMNS_MISMATCH, аналитик правит значения - THEN предупреждение остаётся; документ можно подтвердить вручную (оно подтверждение не блокирует)
Scenario: Бланк 66н в 2025 году без ложного info
- WHEN форма Межениновской за 3 кв. 2025 на бланке 66н с кодом 1120 (в перечне
fsbu4его нет) - THEN редакция документа для проверок ядра —
66n(подпоследовательность длиннее),CODE_NOT_IN_EDITIONу 1120 нет
Scenario: Пропущенная колонка
- WHEN в результате предупреждение
COLUMN_SKIPPEDсcolumn_index = 5, колонки 5 средиcolumns[]нет - THEN запись
document_issueсcolumn_index = 5иcolumn_id = null; экран показывает её у документа
Requirement: Пересчёт после правки
После каждого PATCH формы ядро SHALL пересчитывать checks, needs_review, form_variant, родителей строк detail
и статус точкой входа пакета arm_statements для пересчёта по сохранённым данным (statement-normalization;
конвенция знака — по source_text); временного пересчёта ядра MUST NOT быть. Правила: is_modified или is_confirmed
значения ⇒ needs_review = false; line_code_modified или is_confirmed строки ⇒ needs_review строки = false;
значения строк detail — needs_review = false (Р-23), у строки, выведенной из detail назначением кода,
needs_review её значений считается по общему правилу; акцепт ⇒ жёсткие нарушения не учитываются; правленые ячейки в
голосование знака не входят. Результат пишется: needs_review строк и значений, form_variant, parent_row_index всех
строк detail (ближайшая строка ok выше по row_index; нет такой — null, строка остаётся detail), записи
document_issue видов relation / soft (заменяются целиком независимо от источника), статус. Относительные даты
колонок (period_kind, statement-normalization) пересчитываются от даты отчёта после правки header.report_date и
columns[].period_date. Отличие statements_version задания от версии копии ядра — document_issue
STATEMENTS_VERSION_MISMATCH (info); у задания без версии (1.x) запись не пишется. Записи core
CODE_NOT_IN_DICTIONARY, CODE_NOT_IN_EDITION (только четырёхзначные коды), NAME_MISMATCH и
STATEMENTS_VERSION_MISMATCH пересчитываются заменой; NAME_MISMATCH не ставится строке с назначенным аналитиком кодом;
MAPPING_WARNING, RESULT_STATUS_INCONSISTENT, LEGACY_WARNING сохраняются. Один PATCH — один пересчёт и одна запись
аудита RECOGNIZED_DATA_EDIT. Акцепт SHALL сбрасываться правкой данных — суммы, кода или статуса строки, даты
колонки, поля шапки; подтверждение или снятие подтверждения (confirmed) и подтверждение даты отчёта или даты колонки
тем же значением акцепт MUST NOT сбрасывать (Р-25, S-47).
Scenario: Подтверждение ячейки без правки
- WHEN аналитик подтверждает ячейку с
needs_review = trueбез изменения суммы - THEN
is_confirmed = true,confirmed_by/at,is_modifiedне ставится,needs_review = false, аудит; акцепт проверок, если был, сохраняется
Scenario: Снятие подтверждения
- WHEN аналитик снимает подтверждение с ячейки (
confirmed: false), которая не покрыта соотношением - THEN
is_confirmed = false,needs_review = trueпосле пересчёта, аудит
Scenario: Родитель уходит в расшифровки
- WHEN у строки 1150 две расшифровки (
detail,parent_row_index= 1150), аналитик отмечает саму 1150 расшифровкой - THEN после пересчёта родитель всех трёх строк
detail— ближайшая строкаokвыше 1150; строк над ними нет —null(на экране — в начале раздела)
Scenario: Правка исправляет нарушение
- WHEN форма принята с
RELATION_FAILEDпо1200 = 1210 + … + 1260, аналитик правит сумму 1230 так, что соотношение сходится - THEN запись
relationпо 1200 заменена (её больше нет), статус пересчитан точкой входа пакета
Scenario: После PATCH остаётся RELATION_FAILED
- WHEN аналитик правит сумму, но соотношение всё ещё не сходится
- THEN запись
RELATION_FAILEDпересоздана с новымиexpected/actual;POST …/confirmбез акцепта — 409CHECKS_NOT_PASSED
Requirement: Словари ядра и достройка шаблонных строк
Копия словаря §7 ядра (api/rules/statement_checks.yaml) SHALL быть удалена: проверки ядро считает копией пакета
arm_statements. api/rules/statement_lines.yaml SHALL оставаться только словарём имён (код → наименования по
редакциям): у кода может быть несколько имён (синонимы и законные повторы кода в бланке, Р-20), загрузчик повтор кода в
редакции MUST NOT запрещать; сверка наименования — с любым именем кода; шаблонная строка берёт первое имя. Порядок и
состав редакций — из копии перечня кодов пакета (code_orders.editions); код словаря имён, которого нет в перечне своей
редакции, — ошибка проверки (тест согласованности, sync-statements --check). В редакции fsbu4 словарь имён SHALL быть
дополнен вторыми именами законных повторов 1220 «НДС, начисленный по отгрузке», 1520 «Обязательство по аренде», 2410
«Налог при упрощенной системе налогообложения» (Р-20) и кодами печатной формы 1С 1105 «Гудвил», 1130 «Нематериальные
поисковые активы», 1140 «Материальные поисковые активы», 1530 «Доходы будущих периодов», 2460 «Прочее» (имена — по
fixtures/ocr и шаблонам банка); без этого законные повторы РБ, Счастливочной и aep_6 и любой неуверенный 1105 дают
NAME_MISMATCH. Шаблонные строки достраиваются по словарю имён (одна на код, первое имя); код, у которого есть хотя бы
одна строка ok из файла, шаблоном MUST NOT достраиваться; строки unreadable / missing не заменяются шаблонной строкой
молча.
Scenario: Код словаря вне перечня
- WHEN в
statement_lines.yamlу редакции есть код, которого нет в перечне этой редакции копии пакета - THEN проверка согласованности словаря с перечнем падает
Scenario: Код есть только у строки unreadable
- WHEN у формы нет строки
okс кодом 1250, но есть строкаunreadable«ИЧ,250» - THEN шаблонная строка 1250 достраивается пустой, строка
unreadableпоказывается отдельно со статусом
Scenario: Два имени у кода
- WHEN в редакции
fsbu4словаря имён код 1520 записан дважды — «Кредиторская задолженность» и «Обязательство по аренде» - THEN словарь загружается; шаблонная строка 1520 одна, с именем «Кредиторская задолженность»
Scenario: Гудвил с заголовком раздела в имени
- WHEN строка 1105 (уверенность 67) с наименованием «АКТИВ I. Внеоборотные активы Гудвил»
- THEN справочное «Гудвил» (≥ 5 символов) входит в него — совпало,
NAME_MISMATCHнет
Requirement: Предупреждения файла и ОСВ в API, статус ОСВ при приёме 2.0
recognition_job.metrics.warnings SHALL хранить предупреждения уровня файла структурами {code, severity, message, locator?} (fileWarning контракта): у 2.0 — как пришли, у 1.x — строки, обёрнутые в LEGACY_WARNING info.
JobBrief.warnings карточки файла (FileOut.job) SHALL отдавать список этих структур, а не строк; строки заданий,
сохранённых до выкладки ядра v5.19, при чтении конвертируются в {code: "LEGACY_WARNING", severity: "info", message: <строка>, locator: null} (данные в БД не переписываются). JobBrief.mapping_warnings (предупреждения маппинга ядра)
остаётся списком строк. Это несовместимое изменение типа поля для фронта — фиксируется в гайде фронта.
Предупреждения листа ОСВ результата 2.0 (osvDocument.warnings, коды OSV_*, info) SHALL храниться в
document_issue документа ОСВ (вид warning, источник service) и отдаваться в DocumentOut.issues[] (те же поля,
что issues формы) вместе с предупреждениями маппинга ядра этого листа; DocumentOut.warnings (строки маппинга файла)
сохраняется на окно перехода. osvDocument.status SHALL использоваться: при приёме 2.0 документ ОСВ RECOGNIZED ⇔
osvDocument.status = done ∧ checks листа без нарушений ∧ в metrics.warnings файла нет предупреждений уровня
error ∧ counterparty_check ≠ MISMATCH ∧ нет предупреждений маппинга ядра по этому листу (повтор счёта в файле, строки
без родителя, нарастающая по данным Q1); статус конверта в условие не входит (как Р-19 для форм). При приёме 1.x — как
прежде (§9.3).
Scenario: Карточка файла задания до выкладки
- WHEN у задания, сохранённого до v5.19,
metrics.warnings = ["sheet=ОСВ 60 строки без родителя"] - THEN
JobBrief.warnings = [{code: "LEGACY_WARNING", severity: "info", message: "sheet=ОСВ 60 строки без родителя", locator: null}]
Scenario: Потерянный json в карточке файла
- WHEN результат
2.0scan-gateway несётmetrics.warningsсJSON_FAILED - THEN
JobBrief.warningsсодержит{code: "JSON_FAILED", severity: "error", message, locator}; предупреждение не теряется
Scenario: ОСВ в файле с ОФР, требующим проверки
- WHEN книга из ОСВ (
status = done, контрольные суммы сошлись) и ОФР (needs_check), конвертneeds_check, предупреждений файлаerrorнет - THEN ОСВ
RECOGNIZEDи автоподтверждается; её предупрежденияOSV_*— вDocumentOut.issues
Requirement: CHECKS_NOT_PASSED из document_issue
Нарушения для 409 CHECKS_NOT_PASSED при POST /documents/{id}/confirm формы ББ/ОФР (порядок проверок и условия —
«Подтверждение документа») SHALL читаться только из записей document_issue вида relation без акцепта
(акцептованная форма нарушенной не считается); пересчёта §7 в запросе подтверждения нет. detail.failures[] —
{column_index, relation, expected, actual}: expected и actual — строки Decimal или null (пустая цель при
непустых членах — expected = null); column_index — индекс колонки записи, а если колонку определить нельзя
(у записи нет ни column_id, ни column_index) — null.
Scenario: Жёсткое нарушение из document_issue
- WHEN у формы есть запись
relationбез акцепта и нет строкunreadable/missingсо значениями - THEN 409
CHECKS_NOT_PASSED,detail.failures[]— по записи на нарушение
Scenario: Нарушение с пустой целью
- WHEN у формы нет строки 1700 (потеряна источником), 1300 + 1400 + 1500 заполнены
- THEN 409
CHECKS_NOT_PASSED; вdetail.failures[]нарушение1700 = 1300 + 1400 + 1500сexpected = null
Requirement: Блок checks ответа формы из document_issue
Блок checks ответа GET …/statement SHALL строиться из записей document_issue видов relation (failed) и soft
(soft_failed) с codes, expected, actual, line_ids; собственного расчёта §7 при чтении нет. Элемент нарушения —
{column_index, relation, expected, actual, codes, line_ids}: expected и actual — Decimal или null
(expected = null — цель соотношения пуста при непустых членах); column_index — индекс колонки формы по column_id
записи, иначе column_index записи, иначе -1 (колонки нет: межформенная проверка cross_form всегда -1).
Фактическое различие с detail.failures[] 409 (там отсутствующий индекс — null, а не -1) сохраняется как есть
(P3-60). Межформенная проверка cross_form считается по агрегату формы по коду.
Scenario: checks из document_issue
- WHEN у формы две записи
relationи однаsoft - THEN
checks.failedсодержит две записи,checks.soft_failed— одну; расчёт §7 при запросе не выполняется
Scenario: Нарушение с пустой целью в ответе
- WHEN у формы запись
relationпо1700 = 1300 + 1400 + 1500сexpected = NULL - THEN элемент
checks.failedимеетexpected = null,actual— сумма членов
Requirement: Уточнение шапки ОСВ
PATCH /api/v1/documents/{id}/osv-header SHALL требовать DOCUMENT_EDIT и видимость ЮЛ, принимать частичный набор
period_year, period_quarter, is_cumulative, osv_account_code и возвращать карточку документа с
is_modified, original_values, modified_by, modified_at. Отсутствующие поля SHALL сохраняться.
Правила — §9.5 п.12. Чужой и несуществующий документ SHALL возвращать одинаковый 404.
Scenario: Изменение только счёта
- WHEN аналитик меняет счёт ОСВ РБ с 60 на 62, не передавая период
- THEN меняется только счёт; даже неопределённый квартал остаётся прежним
Scenario: Нет доступа
- WHEN пользователь без DOCUMENT_EDIT правит шапку либо документ находится вне видимости ЮЛ
- THEN без права возвращается 403, при наличии права и отсутствии видимости — 404; данные не меняются
Requirement: Проверки правки шапки ОСВ
После проверки типов тела запроса Pydantic PATCH SHALL проверять последовательно: тип OSV (иначе 409 DOCUMENT_NOT_OSV), файл не SUPERSEDED (иначе 409 FILE_SUPERSEDED), корректность периода (422 INVALID_PERIOD), счёта (422 INVALID_ACCOUNT_CODE), отсутствие другой ОСВ с тем же нормализованным счётом в файле (409 DUPLICATE_ACCOUNT с detail.document_id), редактируемость привязанных версий (409 VERSION_NOT_EDITABLE с detail.version_ids). Все проверки SHALL предшествовать записи.
Scenario: Частичная правка периода
- WHEN передано хотя бы одно поле периода
- THEN итоговые год, квартал и is_cumulative обязательны: год 1900–2099, квартал Q1–Q4, признак boolean; для Q1 признак true; иначе 422 INVALID_PERIOD без изменений
Scenario: Нормализация и коллизия
- WHEN передано 60.1, а у другой ОСВ этого файла уже счёт 60.01
- THEN нормализация даёт 60.01, возвращается 409 DUPLICATE_ACCOUNT с id другого документа
Scenario: Пустой счёт
- WHEN передан пустой счёт
- THEN 422 INVALID_ACCOUNT_CODE, прежний счёт сохранён
Scenario: Порядок конфликтов
- WHEN у заменённого файла документ BALANCE и некорректный период в теле
- THEN возвращается DOCUMENT_NOT_OSV; у OSV того же файла — FILE_SUPERSEDED
Requirement: Исходная шапка и признак исправления
При первой фактической правке ядро SHALL сохранить прежние значения всех четырёх полей в document.original_values; при последующих правках снимок MUST NOT меняться. Новые значения SHALL записываться в ту же строку document, is_modified SHALL отражать отличие от снимка, modified_by/at — последнюю правку. document.id, file_ordinal, строки osv_entry, файл и JSON результата в S3 MUST NOT изменяться.
Scenario: Две правки
- WHEN счёт ОСВ РБ меняется 60 → 62 → 76
- THEN текущий счёт 76, original_values.osv_account_code = 60, в original_values также исходный период; is_modified = true, id и количество документов и строк ОСВ прежние
Scenario: Возврат к исходному
- WHEN все четыре поля возвращены к значениям original_values
- THEN is_modified = false, original_values остаётся заполненным, последняя правка атрибутирована
Scenario: Запрос без изменений
- WHEN нормализованная итоговая шапка совпадает с текущей
- THEN 200 с карточкой без аудита, создания original_values, сброса подтверждения и изменения modified_by/at
Requirement: Снятие подтверждения ОСВ после уточнения
Фактическая правка CONFIRMED ОСВ SHALL установить NEEDS_CHECK, очистить confirmed_by/at и снять привязки consolidation_document из всех версий «в работе», сохранив привязки CANCELLED и статусы версий. Привязка к иной версии SHALL запретить правку. Неподтверждённый документ SHALL сохранить статус. consolidation_lock MUST NOT требоваться; автоподтверждение после PATCH MUST NOT запускаться.
Scenario: Исправление автоподтверждённой ОСВ
- WHEN ОСВ РБ привязана к версии DATA_CORRECTION, аналитик исправляет период
- THEN NEEDS_CHECK, confirmed_by/at = null, привязка снята, версия остаётся DATA_CORRECTION; повторное ручное подтверждение привязывает документ по новому периоду и счёту
Scenario: Завершённая версия
- WHEN документ привязан к VERIFIED, даже если одновременно привязан к версии «в работе»
- THEN 409 VERSION_NOT_EDITABLE со списком запрещающих version_ids, ни одна привязка и шапка не меняются
Scenario: Отменённая версия
- WHEN подтверждённый документ связан только с CANCELLED и версиями «в работе»
- THEN правка разрешена без consolidation_lock, привязки CANCELLED остаются, остальные снимаются
Requirement: Производные данные после уточнения ОСВ
Подходящесть версиям, чек-лист и колонка «ОСВ» SHALL отражать новую шапку при следующем чтении; period_check, counterparty_check и строки osv_entry SHALL сохраняться. Правка MUST NOT запускать автозамену другого файла; замещение привязки с тем же ключом происходит при повторном подтверждении по §3 п.1. Перезагрузка файла MUST NOT переносить уточнения старых документов. Флаг CONSOLIDATION_PERIOD_FROM_VERSION сохраняется.
Scenario: Смена счёта шапки
- WHEN счёт ОСВ РБ исправлен с 60 на 62
- THEN колонка «ОСВ» и подходящие слоты чек-листа учитывают 62; account_code/account_id/account_root строк ОСВ, period_check и counterparty_check не изменились, другой файл ОСВ 62 не заменён
Scenario: Перезагрузка
- WHEN новый загруженный файл распознан с исходным счётом 60
- THEN новые документы созданы из нового результата, исправление 62 предыдущего документа не переносится
Requirement: Строгие типы шапки ОСВ
PATCH SHALL принимать period_year как integer, period_quarter и osv_account_code как string, is_cumulative как boolean, без приведения типов. Отсутствующие поля не меняются; null SHALL передаваться в доменную проверку явно. Неверные типы SHALL отклоняться стандартным 422 валидации тела до доменных проверок. Границы года, допустимые кварталы и совместимость полей SHALL проверяться доменом.
Scenario: Неверный тип года
- WHEN передан period_year как строка, дробное число, boolean, массив или объект
- THEN стандартный 422 с detail-массивом ошибок валидации, доменная обработка не запускается
Scenario: Год вне диапазона
- WHEN у доступного незаменённого документа OSV передан period_year = 2100
- THEN тип integer принят, домен возвращает 422 INVALID_PERIOD
Scenario: Неизвестный квартал
- WHEN у доступного незаменённого документа OSV передан period_quarter = "Q0"
- THEN тип string принят, домен возвращает 422 INVALID_PERIOD