diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index a38087a..1516928 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -660,7 +660,7 @@ replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; ` форма — 409 `DOCUMENT_NOT_STATEMENT`; ячейка не этого документа — 422 `UNKNOWN_CELL`. Исходное сохраняется: у значения при первой правке `original_amount` = прежняя сумма, `is_modified = (amount ≠ original_amount)` (возврат к исходному снимает признак), `modified_by/at`; у поля шапки `*_modified = true`, `*_original_value` не меняется. После правки: акцепт проверок - сбрасывается (§7), проверки и статус пересчитываются (§9.3), пишется одно `audit_event(RECOGNIZED_DATA_EDIT)`; запрос + сбрасывается (§7; прежний акцепт, включая комментарий, остаётся в `before_value.checks_accepted` записи аудита), проверки и статус пересчитываются (§9.3), пишется одно `audit_event(RECOGNIZED_DATA_EDIT)`; запрос без изменений — 200 без аудита. Автозамена при правке не запускается (только при маппинге): правка даты, давшая период другого файла ЮЛ, привязывает документ к версии лишь при подтверждении. Порядок блокировок — как у подтверждения (файл → документ). diff --git a/dev/api-contract-slice-3b.md b/dev/api-contract-slice-3b.md index e35a226..67e61d9 100644 --- a/dev/api-contract-slice-3b.md +++ b/dev/api-contract-slice-3b.md @@ -103,6 +103,8 @@ expected, actual}` (суммы — строками) внутри `detail`: `{"d `inn` пересчитывает `counterparty_check`. Статус документа (`RECOGNIZED`/`NEEDS_CHECK`) пересчитывается после каждой правки. 6. **Акцепт сбрасывается любой правкой формы.** После `PATCH`, который что-то изменил, `checks.accepted` снова `null`. Запрос, который ничего не меняет (те же значения, пустое тело), — 200 с неизменным состоянием, без записи в журнал и без сброса акцепта. + Если акцепт был, в записи журнала `RECOGNIZED_DATA_EDIT` его прежнее значение сохраняется в `before.checks_accepted` + (`by`, `at`, `comment`), поэтому текст сброшенного акцепта можно восстановить из аудита. 7. **`checks.failed` не очищается акцептом.** Акцепт не скрывает нарушения: `failed` остаётся заполненным, а `accepted` непусто — это сигнал «нарушения приняты аналитиком». Блокируют подтверждение только нарушения **без** акцепта. 8. **Что блокирует подтверждение** (`POST …/confirm`): период не определён — 409 `PERIOD_REQUIRED` (исправить датой отчёта); @@ -121,8 +123,8 @@ expected, actual}` (суммы — строками) внутри `detail`: `{"d 12. **Исходный файл.** `GET /files/{id}/content` отдаёт байты через ядро; прямой ссылки на S3 нет. Забирайте как blob с `Authorization` (`` и `` без заголовка не сработают). `Content-Type` — по типу файла (`application/pdf`, `image/png`, `image/jpeg`, для xls/xlsx/xlsb — свои MIME). `Content-Disposition`: `attachment` (по умолчанию) или `inline` (для просмотра в - браузере) с ASCII-именем `file.` и `filename*=UTF-8''…` — исходным именем в percent-кодировке; берите имя из `filename*`. - Работает и для файла `SUPERSEDED`. Объекта нет в хранилище (убран ретеншном) — 404 `FILE_CONTENT_NOT_FOUND` (не путать с `NOT_FOUND`: + браузере) с ASCII-именем `file.` и `filename*=UTF-8''…` — исходным именем в percent-кодировке; берите имя из `filename*`. Ответ несёт + `X-Content-Type-Options: nosniff` (браузер не угадывает тип по содержимому; тип задаёт `Content-Type`). Работает и для файла `SUPERSEDED`. Объекта нет в хранилище (убран ретеншном) — 404 `FILE_CONTENT_NOT_FOUND` (не путать с `NOT_FOUND`: файл в БД есть, а байтов нет); хранилище не подключено — 503 `STORAGE_UNAVAILABLE`. Скачивание в журнал не пишется (OQ-153). 13. **Комментарии.** Только добавление и чтение; правки и удаления нет. `created_at` — порядок показа (по возрастанию). `text` обрезается по краям, пустой после обрезки или длиннее 4000 символов — 422 в формате pydantic. Автор — пользователь запроса. @@ -134,9 +136,13 @@ expected, actual}` (суммы — строками) внутри `detail`: `{"d 15. **CSV чек-листа.** `text/csv; charset=utf-8`, **BOM** в начале, разделитель `;`, переводы строк CRLF. Первая строка — «Организация;ИНН;<правило>…», где правило — «Бух. баланс», «ОФР», «ОСВ <счёт>» (в порядке `rules[]` чек-листа). Ячейки — подписи макета: `CONFIRMED` «Загружен», `PROCESSING` «Обработка», `NEEDS_CHECK` и `RECOGNIZED` «Требуется проверка», `ERROR` «Ошибка - загрузки», `MISSING` «Отсутствует»; при пропущенных кварталах — с хвостом « (нет: Q1, Q2)». Имя файла — - `Content-Disposition: attachment` с `filename*=UTF-8''checklist-<ГК>-<год>-<квартал>.csv`. Забирайте как blob с `Authorization`. - Без `format=csv` маршрут по-прежнему отдаёт JSON `ChecklistOut` (Срез 4). + загрузки», `MISSING` «Отсутствует»; при пропущенных кварталах — с хвостом « (нет: Q1, Q2)». Заголовок ответа — + `Content-Disposition: attachment; filename="checklist.csv"; filename*=UTF-8''checklist-<ГК>-<год>-<квартал>.csv` (ASCII-имя + фиксированное; настоящее имя — в `filename*`, percent-кодировка целиком, кириллица и пробелы экранируются; берите имя оттуда). + Забирайте как blob с `Authorization`. Правило подписи — по `document_type` правила чек-листа; ячейки с текстом (имя ЮЛ) в CSV не + нейтрализуются от формул Excel (design №19, R-9): значение, начинающееся с `=`, `+`, `-`, `@`, Excel может принять за формулу. + Без `format=csv` (по умолчанию `format=json`) маршрут отдаёт JSON `ChecklistOut` (Срез 4); значение `format`, кроме `json` и `csv`, + — 422 (pydantic-массив); право — `CONSOLIDATION_VIEW`, версия вне видимости — 404 `NOT_FOUND`. 16. **`documents_count` у ЮЛ** считается на странице списка и в карточке одним запросом; заменённые файлы (`SUPERSEDED`) не входят. Число документов, а не файлов: один файл с ББ и ОФР даёт 2. 17. **Что не вошло в срез.** Правка кода и названия строки, добавление и удаление строк и колонок, отмена подтверждения, смена diff --git a/dev/qa-guide.md b/dev/qa-guide.md index 2091b7c..83a7bef 100644 --- a/dev/qa-guide.md +++ b/dev/qa-guide.md @@ -501,7 +501,7 @@ curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m js | F14 | ka_go | `POST /documents/{id}/comments` `{"text":"нет подписи руководителя"}`; `{"text":" "}`; затем `GET …/comments` и `GET /files/{file_id}` | 201 с `author.full_name`; пустой после обрезки текст — 422; в списке один комментарий; в карточке файла и `GET /documents/{id}` у документа `comments_count = 1`; комментарий к `CONFIRMED` документу разрешён | | F15 | ka_go | `GET /users/brief?ids=,<случайный uuid>`; с 201 id; с `ids=abc` | 200, один элемент `{id, full_name, is_active}`; 422 `TOO_MANY_IDS`; 422 `INVALID_IDS`; то же под `expert` (без `USERS_ADMIN`) — 200 | | F16 | ka_go | `GET /directories/legal-entities?q=7016006635` после F1 и F12 | `documents_count = 2` (ББ и ОФР) | -| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; подтвердить ББ и ОФР (F11, F12); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` | +| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; подтвердить ББ и ОФР (F11, F12); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename="checklist.csv"` и `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` | | F18 | expert | `PATCH …/statement`, `POST …/comments`, `POST …/accept` | 403 (нет `DOCUMENT_EDIT`); `GET …/statement` и `GET …/comments` — 200 | | F19 | client_manager | `GET …/statement`, `GET /files/{id}/content` документа ЮЛ другого филиала | 404 `NOT_FOUND`, тело как у случайного uuid | | F20 | ka_go | `GET …/statement` документа `OSV` (раздел 6.6) | 409 `DOCUMENT_NOT_STATEMENT` |