Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
6.7 KiB
directory-import Specification
Purpose
Импорт справочников ГК / ЮЛ / ГСК / ГСЗ / филиалов из выгрузки ЕЦП CRM (xlsx или csv) по правилам §13
05-domain-rules.md: построчные решения с кодами, одна транзакция, dry_run, стратегия дублей, отчёт в ответе,
в audit_event(DIRECTORY_IMPORT) и файл в S3.
Requirements
Requirement: Приём файла и формат
POST /api/v1/directories/import (multipart: file; поля dry_run (по умолчанию true), on_existing ∈ {skip, update},
по умолчанию skip; право DIRECTORY_ADMIN) SHALL принимать xlsx (первый лист) или csv (UTF-8, разделитель ; или ,)
размером до 20 МБ, сопоставлять заголовки по нормализованному тексту (ИНН, iD ГК, Базовый РФ ГК в CRM, Группа компаний, Организация, ГСК, ГСЗ, Тип; порядок и лишние колонки не важны). Отсутствие любой из обязательных колонок
(ИНН, iD ГК, Группа компаний, Организация, Тип) → 422 MISSING_COLUMNS с перечнем; нечитаемый файл → 422
UNSUPPORTED_FILE; превышение размера → 413.
Scenario: Реальная выгрузка принята
- WHEN загружается
fixtures/directories/ecp-export-russkiy-biskvit.xlsxсdry_run = true - THEN ответ 200 с отчётом:
rows = 21, ошибок файла нет
Scenario: Нет колонки «Тип»
- WHEN в csv отсутствует колонка
Тип - THEN ответ 422
MISSING_COLUMNS, в отчёте перечислена недостающая колонка, в БД ничего не изменено
Requirement: Построчные правила и коды отчёта
Для каждой строки импорт SHALL применять §13.2 и присваивать ровно один итоговый код из перечня: UNSUPPORTED_TYPE,
INVALID_INN, DUPLICATE_IN_FILE, LE_IN_OTHER_GROUP, GK_NAME_CONFLICT (строка пропущена) или LE_CREATED, LE_UPDATED,
LE_EXISTS (строка применена), плюс сопутствующие события RF_CREATED, GK_CREATED, GK_UPDATED, GK_EXISTS,
GSK_CREATED, GSZ_CREATED, MEMBER_ADDED, UNKNOWN_FLAG. Тип вне {ЮЛ, ИП} (ASSUMPTION(OQ-87)), ИНН не 10/12 цифр
(ASSUMPTION(OQ-88)) и ИНН, уже встречавшийся в файле, пропускаются. Филиал по имени создаётся при отсутствии
(ASSUMPTION(OQ-89)). ЮЛ, существующее в другой ГК, не переносится (ASSUMPTION(OQ-90)). Флаги «входит в ГСК»/«входит в ГСЗ»
создают не более одной ГСК и одной ГСЗ на ГК с именем ГК (ASSUMPTION(OQ-91)). Отсутствие флага членство NOT закрывает.
Scenario: Срез «Русский Бисквит», пустая база
- WHEN импорт
ecp-export-russkiy-biskvit.xlsxсdry_run = false,on_existing = skipна базе послеseed - THEN создана 1 ГК (
crm_id = 1-AZFL6BQ, имя «Русский Бисквит»), 1 филиал «Санкт-Петербургский РФ» (RF_CREATED), 20 ЮЛ/ИП (LE_CREATED: 18 ЮЛ и 2 ИП), 1 ГСК с именем ГК и двумя членами (АО «Русский бисквит», ООО «Пекарня Счастливочная»), 0 ГСЗ; строка типаФЛ(идёт после ИП с тем же ИНН) →UNSUPPORTED_TYPE(тип проверяется первым,DUPLICATE_IN_FILEв этом файле не возникает)
Scenario: Повторный импорт идемпотентен
- WHEN тот же файл импортируется второй раз с
on_existing = skip - THEN все применённые строки получают
LE_EXISTS/GK_EXISTS, число строк в таблицах не меняется
Scenario: Обновление имени
- WHEN в файле изменено имя существующего ЮЛ и импорт идёт с
on_existing = update - THEN имя обновлено, в
legal_entity_historyснимокUPDATE, код строкиLE_UPDATED
Scenario: ЮЛ в другой ГК
- WHEN ИНН уже состоит в другой ГК
- THEN строка пропущена с
LE_IN_OTHER_GROUP, ЮЛ не переведено, импорт продолжается
Requirement: Транзакция, dry_run, след
Импорт SHALL выполняться в одной транзакции: ошибки строк не прерывают его, при dry_run = true транзакция откатывается,
а отчёт идентичен боевому. При dry_run = false SHALL быть записан audit_event(action = DIRECTORY_IMPORT, section = DIRECTORIES, object_type = 'directory_import', after_value = отчёт, description = имя файла) и файл сохранён в S3
uploads/imports/{audit_event_id}/{original_name} с регистрацией в s3_object (bucket_role = uploads,
created_by = user:{id}); при dry_run ни аудита, ни файла. Отчёт SHALL содержать счётчики (rows, groups_created,
groups_updated, groups_skipped, entities_created, entities_updated, entities_skipped, memberships_created,
offices_created, errors) и построчный список {row, inn, code, message}.
Scenario: dry_run не оставляет следов
- WHEN импорт выполнен с
dry_run = true - THEN число строк в
company_group,legal_entity,regional_office,audit_event,s3_objectне изменилось, отчёт возвращён
Scenario: Полная выгрузка
- WHEN
docs/source/biq/ecp-export-lagal-entity.xlsxимпортируется сdry_run = true(маркерfixtures,ARM_KA_DOCS) - THEN ответ 200 за ≤ 60 с,
rows = 8400, в отчёте есть кодыUNSUPPORTED_TYPE(≥ 500),INVALID_INN,DUPLICATE_IN_FILE,RF_CREATED= 53,groups_created= 344 (из 345 ГК файла одна целиком состоит из строк-дублей и не создаётся)