Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
10 KiB
primary-files Specification
Purpose
Загрузка файлов первичной отчётности в ядро: физический файл в S3 и stored_file, задание распознавания в outbox,
дедуп, жизненный цикл статуса файла (§9.2), автозамена прежних версий и удаление.
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) и 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). Ответ SHALL
перечислять принятые файлы (accepted[] с file_id, job_id, name) и отклонённые (rejected[] с name, code,
message); код ответа 201, если принят хотя бы один, иначе 422. Тип документа и период при загрузке MUST NOT
запрашиваться (макет 40). Без доступного S3 — 503 STORAGE_UNAVAILABLE.
Необязательный query-параметр consolidation_version_id SHALL проверяться до приёма файлов: версия видима (иначе 404
NOT_FOUND с сообщением VERSION_NOT_FOUND), статус «в работе» (иначе 409 VERSION_NOT_EDITABLE), ЮЛ ∈ периметру (иначе 422
ENTITY_NOT_IN_PERIMETER); если принят хотя бы один файл и версия в INITIATED, она SHALL перейти в
DOCUMENTS_PROCESSING (changed_by = загрузивший) в отдельной транзакции после приёма файлов. Блокировка версии
для загрузки MUST NOT требоваться.
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 файла;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, ЮЛ в периметре, загружен один принятый файл с?consolidation_version_id= - THEN 201; версия в
DOCUMENTS_PROCESSING, запись истории сchanged_by= пользователь
Scenario: ЮЛ вне периметра
- WHEN
consolidation_version_idуказан, ЮЛ не в периметре версии - THEN 422
ENTITY_NOT_IN_PERIMETER, файлы не приняты
Requirement: Дедуп по содержимому
Файл с тем же sha256, что у существующего stored_file того же ЮЛ со статусом не SUPERSEDED, SHALL отклоняться с
кодом DUPLICATE_FILE и file_id существующего. Тот же файл у другого ЮЛ SHALL приниматься.
Scenario: Повторная загрузка того же файла
- WHEN тот же xls загружается во второй раз для того же ЮЛ
- THEN
rejected[0].code = DUPLICATE_FILE,rejected[0].file_id— прежний файл; новых записей нет
Requirement: Жизненный цикл статуса файла
stored_file.status SHALL меняться только по заданию current_recognition_job_id: UPLOADED при загрузке;
PROCESSING при ack сервиса; ERROR при финальной ошибке задания (error, timeout, dlq) и при невозможности
маппинга; PROCESSED после успешного маппинга; SUPERSEDED при автозамене. Карточка файла SHALL показывать причину
ERROR (error_code/error_message задания или ошибку маппинга).
Scenario: Парсер вернул ошибку
- WHEN конверт
error UNSUPPORTED_FORMATдля задания файла - THEN
stored_file.status = ERROR, в карточке файлаerror_code = UNSUPPORTED_FORMAT
Scenario: Сервис не ответил
- WHEN задание переведено вахтёром в
timeout - THEN файл —
ERRORсerror_code = TIMEOUT
Requirement: Автозамена прежних версий
После успешного маппинга файла ядро SHALL найти другие файлы того же ЮЛ со статусом не SUPERSEDED, у которых есть
документ с теми же document_type, osv_account_code, period_year, period_quarter, и перевести их в
SUPERSEDED (superseded_by_file_id = новый файл, updated_at); новый файл получает version_no = максимум среди
заменённых + 1. Документы и строки заменённых файлов SHALL сохраняться (история), но по умолчанию не показываться.
Привязки consolidation_document документов заменённых файлов к версиям «в работе» SHALL удаляться в той же
транзакции (версии блокируются FOR UPDATE по возрастанию id после старых файлов); привязки к версиям в других
статусах не трогаются; статус версии не меняется. Аудит FILE_REPLACE (object_id заменённого, after_value с
новым файлом и unbound_version_ids, в description — перечень заменённых документов и признак confirmed, если
заменён документ CONFIRMED). Решение владельца 05.10.2026: замена происходит и для CONFIRMED.
Scenario: Новая ОСВ 60 за 1 кв. 2025
- WHEN у ЮЛ есть
PROCESSEDфайл с документом OSV 60 / 2025 / Q1 (CONFIRMED), загружен и смапплен новый файл с таким же документом - THEN прежний файл —
SUPERSEDED,superseded_by_file_id= новый, у новогоversion_no = 2;audit_event(FILE_REPLACE)сdescription, содержащимconfirmed=true; список файлов ЮЛ по умолчанию показывает только новый
Scenario: Заменённый документ был в версии
- WHEN заменённый
CONFIRMEDдокумент ОСВ 60 за 2024 Q2 был привязан к версии 2024 Q3 вDATA_CORRECTION - THEN привязка удалена; в чек-листе ячейка ОСВ 60 показывает
missing_quarters = ["Q2"](илиNEEDS_CHECKпо новому документу), пока новый документ не подтверждён; статус версии прежний
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 = карточка файла). Удаление MUST быть отклонено 409 FILE_HAS_CONFIRMED_DOCUMENTS, если у файла есть
документ CONFIRMED, и 409 FILE_SUPERSEDES_OTHERS, если на файл ссылаются superseded_by_file_id. Файл
SUPERSEDED удалить можно.
Scenario: Удаление ошибочного файла
- WHEN файл в
ERRORудаляется владельцем - THEN 204;
stored_file,recognition_jobотсутствуют;s3_object.expires_atзаполнен;audit_event(FILE_DELETE)
Scenario: Подтверждённый документ
- WHEN удаляется файл с документом
CONFIRMED - THEN 409
FILE_HAS_CONFIRMED_DOCUMENTS, ничего не изменилось