2026-11-07 04:01:45 +04:00

9.1 KiB
Raw Blame History

Spec Delta

Purpose

Дельта change quality-metrics (метрики качества первички).

MODIFIED Requirements

Requirement: Загрузка файлов организации

POST /api/v1/legal-entities/{legal_entity_id}/files (multipart, одно или несколько полей files; право DOCUMENT_EDIT; ЮЛ в области видимости пользователя, иначе 404) SHALL для каждого файла в отдельной транзакции: определить тип по расширению и сверить с сигнатурой содержимого (xls — BIFF8, xlsx/xlsb — zip, pdf, png, jpg); отвергнуть файл больше UPLOAD_MAX_BYTES (50 МиБ) или неподдерживаемого типа; вычислить sha256; создать s3_object (bucket_role = uploads, ключ uploads/{file_id}/{job_id}/{безопасное имя}, sha256, size_bytes, content_type, created_by = user:{id}), положить объект в S3, создать stored_file (status = UPLOADED, version_no = 1, uploaded_by, uploaded_at, content_hash, original_name, consolidation_id = NULL) и recognition_job (status = queued, pipeline по типу: xls/xlsx/xlsb → tabular, pdf/png/jpg → image; max_attempts 3 / 1), установить stored_file.current_recognition_job_id, записать audit_event(FILE_UPLOAD, PRIMARY_DOCUMENTS) с after_value.consolidation_id = null. Ответ SHALL перечислять принятые файлы (accepted[] с file_id, job_id, name) и отклонённые (rejected[] с name, code, message); код ответа 201, если принят хотя бы один, иначе 422. Тип документа и период при загрузке MUST NOT запрашиваться (макет 40). Без доступного S3 — 503 STORAGE_UNAVAILABLE. Query-параметр consolidation_version_id MUST NOT приниматься (удалён в v5.18): загрузка этим маршрутом — без контекста консолидации и не меняет статусы версий; загрузка в консолидацию — требование «Загрузка файлов в консолидацию». Для PDF stored_file.is_scan SHALL определяться при загрузке по наличию текстового слоя в первых 10 страницах (разбор pypdf — вне event loop, asyncio.to_thread): нет текста — скан (true), есть — цифровой (false); для не-PDF — NULL. PDF со скрытым OCR-слоем считается цифровым.

Scenario: Реальная ОСВ принята и ушла в outbox

  • WHEN ka_rf загружает rb/2024-FY/ОСВ_66 за 2024.xls для ЮЛ своего филиала
  • THEN 201, accepted[0].file_id существует; stored_file.status = UPLOADED, file_type = XLS, content_hash = sha256 файла, consolidation_id IS NULL; recognition_job.status = queued, pipeline = tabular; объект в S3 по ключу uploads/{file_id}/{job_id}/ОСВ_66 за 2024.xls; публикатор outbox публикует запрос с этим ключом

Scenario: Смесь принятых и отклонённых

  • WHEN в одном запросе xlsx, файл .docx и файл 51 МиБ
  • THEN 201, accepted — один, rejected — два с кодами UNSUPPORTED_FILE и FILE_TOO_LARGE

Scenario: Расширение не совпадает с содержимым

  • WHEN загружается файл osv.xlsx, байты которого — BIFF8
  • THEN файл отклонён с кодом UNSUPPORTED_FILE

Scenario: Чужое ЮЛ

  • WHEN ka_rf загружает файл для ЮЛ другого филиала
  • THEN 404, объектов и записей нет

Scenario: Загрузка в контексте версии

  • WHEN у ГК ЮЛ есть версия INITIATED с этим ЮЛ в периметре, файл загружен маршрутом ЮЛ с query consolidation_version_id (как делал клиент до v5.18)
  • THEN 201, параметр игнорируется; stored_file.consolidation_id IS NULL; версия остаётся INITIATED

Scenario: ЮЛ вне периметра

  • WHEN ЮЛ не входит в периметр ни одной версии, файл загружен маршрутом ЮЛ
  • THEN 201 — маршрут ЮЛ периметр не проверяет; проверка периметра — только у загрузки в консолидацию

Scenario: Признак скана

  • WHEN загружается PDF без текстового слоя
  • THEN stored_file.is_scan = true; у xlsx is_scan IS NULL

Requirement: Удаление файла

DELETE /api/v1/files/{file_id} (DOCUMENT_EDIT, policy) SHALL удалить строки ОСВ, документы, задания и stored_file, проставить s3_object.expires_at = now() (объект физически не удаляется, OQ-22) и записать audit_event(FILE_DELETE, before_value = карточка файла, after_value = {unbound_version_ids}). Удаление MUST быть отклонено 409 FILE_HAS_CONFIRMED_DOCUMENTS, если у файла есть документ CONFIRMED, подтверждённый пользователем (confirmed_by не NULL), или если автоподтверждённый документ файла (confirmed_by NULL) привязан к версии в статусе, отличном от INITIATED, DOCUMENTS_PROCESSING, DATA_CORRECTION (OQ-157, уточняет OQ-110); и 409 FILE_SUPERSEDES_OTHERS, если на файл ссылаются superseded_by_file_id. При удалении файла с автоподтверждёнными документами их привязки consolidation_document SHALL сниматься, статус версий MUST NOT меняться, after_value.unbound_version_ids — id версий, где привязки сняты (по возрастанию; пустой список, если привязок не было). Блокировки: задания → файл → документы → advisory-блокировка привязок ГК → версии по id. Файл SUPERSEDED удалить можно. Если у файла есть quality_intake_fact (по file_id), объект uploads файла MUST NOT получать expires_at — он нужен эталонному прогону качества; сырой ответ ИС (raw) при удалении expires_at не получает никогда. Для документов с исходом удаление SHALL писать факт исхода DELETED / DELETED_AFTER_CONFIRM в той же транзакции (спека quality-metrics).

Scenario: Удаление ошибочного файла

  • WHEN файл в ERROR удаляется владельцем
  • THEN 204; stored_file, recognition_job отсутствуют; s3_object.expires_at заполнен; audit_event(FILE_DELETE) с after_value.unbound_version_ids = []

Scenario: Подтверждённый документ

  • WHEN удаляется файл с документом CONFIRMED, у которого confirmed_by = пользователь
  • THEN 409 FILE_HAS_CONFIRMED_DOCUMENTS, ничего не изменилось

Scenario: Автоподтверждённый документ в корректировке

  • WHEN версия 2025 Q1 в DATA_CORRECTION, к ней привязан автоподтверждённый документ ОСВ 60 РБ; файл удаляется
  • THEN 204; привязка снята, версия осталась в DATA_CORRECTION; FILE_DELETE.after_value.unbound_version_ids = [id версии]

Scenario: Версия ушла на согласование

  • WHEN автоподтверждённый документ файла привязан к версии в CONSOLIDATION_RF
  • THEN 409 FILE_HAS_CONFIRMED_DOCUMENTS, привязка и файл на месте

Scenario: Автоподтверждённый без привязок

  • WHEN удаляется файл, все документы которого автоподтверждены и ни к одной версии не привязаны
  • THEN 204; after_value.unbound_version_ids = []

Scenario: Файл с фактом приёма

  • WHEN удаляется файл, по документам которого записан quality_intake_fact
  • THEN 204; объект uploads и сырой ответ остаются без expires_at, результат (results) экспирится; записан факт исхода