277 lines
26 KiB
Markdown
277 lines
26 KiB
Markdown
# 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 без родителя, у строк уровня 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`, `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`, строка с которым в форме уже есть, код вне справочника редакции или редакция не
|
||
определена — 422 `UNKNOWN_CELL`; колонка не этой формы — 422 `UNKNOWN_CELL`; одна ячейка дважды в запросе — 422
|
||
`DUPLICATE_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`; в ответе у 1700 `id` заполнен, `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 с одинаковым телом
|