// ДКБ · Модель данных v5.18: справочники, файлы/распознавание, консолидация, КФО, аудит, ЕХФД // Формат: DBML (dbdiagram.io). Вставить на https://dbdiagram.io/d // >>> Источник истины по модели данных. Аналитика в Confluence ссылается сюда. // Над каждой таблицей — блок «ИСТОЧНИК»: пункт ТЗ АРМ КА BIQ-34 / ТЗ ЕХФД (помечено «ЕХ ТЗ») / Методика; // страница аналитики (md); экран макета dkb_24.09.26; версия модели, в которой таблица появилась (v4 = исходная DBML аналитики). // // v5.18 (решения владельца 09.10.2026; RSHB/arm-ka-docs#90, openspec/changes/console-files): // 1. stored_file.consolidation_id (uuid, nullable, FK → consolidation): консолидация (ГК + год + квартал), в контексте // которой загружен файл, — основа «рабочего стола консоли» (полный состав стола — context/05-domain-rules.md §9.5 п. 11), общего для всех версий периода. NULL — загрузка без контекста // (страница ЮЛ, монозаёмщики, API). Пишется один раз при загрузке (POST /consolidations/{id}/legal-entities/{le}/files); // при автозамене новый файл без контекста наследует consolidation_id заменённого. Индекс (legal_entity_id, consolidation_id). // Что использует конкретная версия, по-прежнему определяет consolidation_document; заморозка после VERIFIED — привязки и ex_snapshot. // Миграция 0006 arm-core: backfill по consolidation_document (при нескольких периодах — самый ранний). // 2. Note recognition_job.reply_deadline: срок ответа OCR — 20 мин (OQ-159, решение владельца 09.10.2026), структура не меняется. // // v5.17 (решение владельца 07.10.2026, пересмотр OQ-31; RSHB/arm-ka-docs#53): // 1. legal_entity.kpp и legal_entity_history.kpp (varchar, nullable): КПП юридического лица. Формат — 9 символов: // 4 цифры, 2 символа (цифра или латинская A–Z), 3 цифры; иначе INVALID_KPP. У ИП КПП запрещён (INVALID_KPP; // импорт — строка пропускается). Для ЮЛ необязателен: существующие записи и импорт без колонки КПП остаются валидными. // Уникальность — только legal_entity.inn (КПП — атрибут, сопоставление контрагентов ОСВ по ИНН не меняется). // Изменение КПП — обычная правка со снимком в legal_entity_history. Отменяет п. 2 v5.14 («КПП не вводится»). // // v5.16 (Срез 4, решения владельца 06.10.2026): // 1. Новый enum counterparty_match_kind и колонка osv_entry.counterparty_match_kind (nullable): способ, которым строке ОСВ // сопоставлен контрагент. NULL ⇔ counterparty_id NULL; INN / NAME / ALIAS — шаг автоматического резолва при маппинге // (ИНН; точное имя; нормализованное имя или алиас); MANUAL — ручное сопоставление аналитиком; PROPAGATED — // автоприменение по имени в версии (DOC-221278 п. 12–13). Миграция 0004 заполняет колонку по уже смапленным строкам. // 2. Без изменений структуры, уточнение семантики consolidation_document: строка создаётся при подтверждении документа // (по документам ЮЛ — членов периметра версии «в работе»), при подходящем подтверждённом документе — при добавлении ЮЛ // в периметр; удаляется при отвязке документа (замена файла, удаление, исключение ЮЛ из периметра). Для одной // (ЮЛ, document_type, osv_account_code, период, признак нарастающего) привязан один документ — самый поздний confirmed_at. // 3. Без изменений структуры, уточнение семантики consolidation_lock: блокировка без истечения по времени — активна, пока её не // снял владелец, администратор или перехват другим пользователем с CONSOLIDATION_EDIT по причине (OQ-119). // // v5.15 (Срез 2c, решения владельца 05.10.2026): // 1. osv_data_type: добавлено NU — строки налогового учёта из ОСВ «БУ, НУ» хранятся в osv_entry как есть (OQ-45); // контракт результата допускал NU с v1.0. // 2. app_user.email хранится в нижнем регистре: нормализация при записи (seed, администрирование), поиск по // lower(email) остаётся; уникальность — существующий UNIQUE по email (OQ-59). Структура не меняется. // 3. Без изменений структуры, уточнение семантики: stored_file.status — PROCESSING ставится inbox'ом по ack сервиса, // PROCESSED/ERROR — циклом маппинга результата (OQ-67); маппинг выполняется отдельно от inbox (OQ-69); // recognition_job.group_schema заполняет маппер; «Итого» в osv_entry не хранится (OQ-01). // 4. Индексы для удаления файла и обхода дерева строк: osv_entry (parent_entry_id), osv_entry (recognition_job_id), // stored_file (current_recognition_job_id) — проверки FK при DELETE документа/файла и очистке перед повторным // маппингом шли seq scan'ом (OQ-117). Замер: DELETE документа 1000 строк 8,6 с → 16 мс на 200 тыс. строк. // // v5.14 (Срез 1, решения владельца 04.10.2026): // 1. ЮЛ может состоять ОДНОВРЕМЕННО в одной ГСК и одной ГСЗ (но только в одной группе каждого вида) — отменяет // ANL-030/v5.8. В related_group_member возвращена денормализованная колонка kind (= related_group.kind, // равенство проверяет ядро при записи); частичный уникальный индекс стал (legal_entity_id, kind) WHERE is_current. // 2. КПП у ЮЛ не вводится (OQ-31: поле есть только в макете; решение владельца — не нужен). [Отменено в v5.17 — КПП введён.] // 3. Без изменений структуры, уточнение семантики: company_group.crm_id = «iD ГК» выгрузки ЕЦП CRM // (docs/source/biq/ecp-export-lagal-entity.xlsx), base_regional_office — «Базовый РФ ГК в CRM»; флаги «входит в ГСК/ГСЗ» // выгрузки → членство в одной неявной ГСК/ГСЗ на ГК (правила импорта — 05-domain-rules.md §13). // 4. audit_action: добавлены LEGAL_ENTITY_CHANGE (создание/изменение/закрытие/перевод ЮЛ, алиасы) и USER_CHANGE // (создание/деактивация пользователя администратором) — ТЗ 8.2 «журнал изменений справочников» их требует, // в v5.13 были только GROUP_CHANGE / MEMBERSHIP_CHANGE / USER_ROLE_CHANGE. // // v5.13 (ревью 30.09–01.10, замечания 1–7; п.2 parent_entry_id — отложен): // 1. Права как данные: permission + role_permission (матрица доступов ТЗ сноска 29 — в БД, не в коде); // права только через роли, прямых назначений пользователю нет. Отменяет решение v5.4. // 3. osv_group_schema убрана — схема группировок шапки = recognition_job.group_schema jsonb; // тип строки — osv_entry.group_type. // 4. financial_statement_value.needs_review убран — производное от confidence/is_recognized, считается запросом. // 5. recognition_job: оставлен только result_s3_object_id; сырой ответ ИС Сканирование в БД не регистрируется // (scan-gateway пишет его в S3 по конвенции raw/{job_id}.json для разбора инцидентов). // 6. stored_file.fields_modified убран — COUNT(is_modified) по данным документа. // 6-бис. Разделение «файл / документ»: stored_file = физический файл (один на загрузку), document = логическая // единица (ББ, ОФР, ОСВ по счёту, расшифровка) — один файл содержит N документов (PDF с ББ+ОФР, xlsx с // несколькими ОСВ листами). Чек-лист, проверки периода/контрагента, подтверждение, FK разобранных данных, // consolidation_document и ex_snapshot_item — на document. Замена (superseded_by) остаётся на файле целиком. // 7. file_type: убран P7. // // Изменения относительно v4 (кратко): // FIX — исправлены дефекты v4 (PK у meta, типы, копипаст-note про «контрагента», row_type, parent_entry_id) // ADD — audit_event, status_history, company_group_assignment, elimination_rule, statement_check_result, // consolidated_osv_line, chess_cell, vgo_pair_match (агрегат по ТЗ 6.4), ofr_vgo_match, kfo_result_*, // client_kfo_line, comment, export_log/preset, ex_snapshot*, correction_version // CHG — role → 7 ролей ТЗ + сегмент (ЗО); consolidation_version — сценарий/подход/гудвилл/решение УОБ; // stored_file — счёт ОСВ, версии/замена; osv_entry — признак контрагента + override; legal_entity — монозаёмщик // // Ключи: у ВСЕХ сущностей суррогатный PK (uuid). Натуральные ключи — UNIQUE-колонки. // (Для высокообъёмных таблиц — osv_entry, audit_event — uuid можно заменить на bigint identity.) // ЮЛ и ГК — SCD Type 4 (живая таблица + *_history). Членство в ГСК/ГСЗ — SCD Type 2 (is_current). // Первичка не мутируется: правки распознанных значений = поле + *_original_value + is_modified + audit_event. // Производные объекты версии (сводная ОСВ, шахматка, КФО) — snapshot на consolidation_version: // пересчитываются пока версия «в работе», замораживаются при VERIFIED (ТЗ 6.10, 7.2). // // DECISION-1 (ТЗ 6.4 vs v4): первичная модель сопоставления ВГО — АГРЕГАТ по паре компаний × правилу пары счетов // (vgo_pair_match), как в ТЗ/Прил. 3. Строковое 1:1 сопоставление (vgo_match) сохранено как детализация агрегата. // DECISION-2 (ЕХФД): ЕХ — те же таблицы + ex_snapshot как «замороженный указатель» на состав верифицированной версии. // Отдельная физическая БД для ЕХ не предполагается; архив в data lake — выгрузка снапшота. // DECISION-3: ГО — обычная запись regional_office с is_head_office=true (вместо NULL). // DECISION-5 (производные консолидации): сводная ОСВ, пары ВГО и шахматка НЕ хранятся, пока версия в работе — // считаются запросом из osv_entry (актуальные прогоны) × consolidation_member × elimination_rule. // Признак контрагента ГСК/ГК/Внешний — функция от osv_entry.counterparty_id и периметра версии; ручное действие // аналитика = сопоставление контрагента с ЮЛ (counterparty_id + алиас), комментарий — в audit_event. // v5.16: способ сопоставления контрагента (osv_entry.counterparty_match_kind) ХРАНИТСЯ; пробелы квартальной склейки и сам свод — нет. // При VERIFIED производные замораживаются один раз: структурированный снапшот — jsonb в ex_snapshot_item.payload // (просмотр в ЛК ЕХ), подготовленный xlsx для выгрузки — s3_object. Отдельная NoSQL-база не вводится. // Корректировки и гудвилл ссылаются на пару ВГО по естественному ключу (правило, компания 1, компания 2). // DECISION-4 (распознавание): два отдельных stateless-сервиса без доступа к БД — // tabular-parser (XLS/XLSX/XLSB) и scan-gateway (адаптер ИС Сканирование для PDF/PNG/JPG). // Запрос: NATS recognition.{tabular|image}.request + файл в S3 uploads/. Результат: JSON в S3 results/ + // NATS recognition.result. Единственный писатель в БД — arm-core; корреляция по recognition_job.id; // идемпотентность по msg_id; outbox при создании recognition_job; DLQ. // v5.13: результат одного файла содержит N документов; ядро создаёт document при маппинге результата. // // v5.1 (по макету dkb_24.09.26): consolidation_lock (взятие в работу), participant_disclosure (расшифровки участника), // legal_entity_history.event_type, statement_meta реквизиты (ОКПО/ОКОПФ/ОКФС/адрес), kfo_result_line: vgo/share, // client_kfo_line: ВГО клиента/банка + причина, audit_event: section/result/user_agent, статусы REJECTED/ARCHIVED, // document_check (проверка периода/контрагента файла), company_group.crm_id nullable (ANL-037). // v5.2 (DECISION-4): recognition_job: subject/msg_id/deadline/external_task_id/result_schema_version/error_code, статусы timeout/dlq. // v5.12 (ревью part09): ЕХ самодостаточен — срез не ссылается FK на консолидацию и её результаты (их удаляют по сроку, // срез хранится «вечно»): ex_snapshot.source_version_id/no без FK; ex_snapshot_item = stored_file_id (исходники храним) // ИЛИ payload jsonb (сводная ОСВ, ВГО, шахматка, КФО, гудвилл, корректировки); убраны object_type/object_id, // prepared_s3_object_id (готовые файлы не храним), ex_snapshot.source_system, audit_event.s3_object_id; // добавлен ex_snapshot.superseded_by_snapshot_id (архивный → актуальный). // v5.11: consolidation_version.elimination_rules_version — какая версия правил Прил. 3/4 применялась (объяснимость и // воспроизводимость расчёта замороженной версии). // v5.10 (DECISION-5, ревью part08): убраны consolidated_osv_line, consolidated_osv_source, vgo_pair_match, chess_sheet, chess_cell, // osv_entry.counterparty_type (выводится); correction ссылается на пару по (rule_id, company_1_id, company_2_id); // ex_snapshot_item получил payload jsonb. // v5.9 (ревью part06/07): statement_check_result убрана — проверки считаются на лету, акцепт КА — на документе // (financial_statement_meta.checks_accepted_*); reviewed_by/at перенесены в meta (документ ревьюится целиком); // financial_statement_line: убраны ordinal/row_kind/section (порядок и группирующие строки — шаблон формы на фронте); // consolidation: company_group_id NOT NULL, related_group_id nullable — ГСК фиксируется при VERIFIED (ANL-029); // consolidation_version: убран package_uuid (документы в ИС Сканирование по одному, ключ = recognition_job.id), добавлен updated_by. // v5.8 (ревью part01): убраны company_group_assignment (видимость ТЗ 8.1 выводится из company_group.base_regional_office_id, // legal_entity.regional_office_id, app_user.regional_office_id; история перезакрепления — company_group_history), // legal_entity.is_mono_borrower (монозаёмщик = company_group_id IS NULL), related_group_member.kind и индекс related_group(id, kind) // (после ANL-030 partial-unique по legal_entity_id без условия на kind). // v5.7: метрики качества распознавания — recognition_job.fields_total/low_confidence/unrecognized (что пришло от сервиса). // (stored_file.fields_modified/fields_reviewed и financial_statement_value.needs_review — убраны в v5.9/v5.13 как производные.) // v5.6 (ревизия 28.09, решения по каждой убранной таблице): // account_code_alias — нормализация кодов в коде; редкие субсчета клиентов добавляются в chart_of_account // vat_rate — константы; средневзвешенная ставка хранится в vgo_pair_match // crm_import_batch — импорт справочников только на старте (миграция); след в audit_event(DIRECTORY_IMPORT) + файл в s3_object // osv_entry_edit — audit_event(RECOGNIZED_DATA_EDIT) + osv_entry.original_values/modified_by/modified_at // document_check — колонки period_check/counterparty_check (с v5.13 — на document); сверка с периодом версии вычисляется // export_preset — выбор колонок в момент экспорта, без сохранения наборов // export_log — audit_event(EXPORT, after_value={report_code, format, columns, rows}); файлы выгрузок не хранятся (S3 lifecycle exports/ = 7 дней) // v5.5: убрана correction_version — история корректировок (сноска 22) ведётся audit_event(before/after), version_no в correction. // v5.4: возвращён s3_object (реестр объектов S3 — lifecycle/ретеншн ТЗ 8.4, sha256, единая точка ключей); permission/role_permission — матрица в коде (ОТМЕНЕНО в v5.13). // v5.3 РЕВИЗИЯ по правилу «в базе только то, что есть в ТЗ или на экране макета». Удалено 14 таблиц: // - account_code_alias: нормализация кодов (60.1=60.01) — алгоритм в коде (ARCH-004), не справочник // - vat_rate: ставки 0/10/20/22 — константы; средневзвешенная ставка хранится в vgo_pair_match.vat_weighted_rate // - disclosure_item: расшифровки участника: в UI скрыты, в ТЗ только тип документа «Расшифровка» → вне scope до решения ANL-040 // - participant_disclosure: см. disclosure_item // - crm_import_batch: журнал импортов не в ТЗ/UI; факт импорта и ошибки — audit_event(GROUP_CHANGE/IMPORT) // - permission / role_permission: ВОЗВРАЩЕНЫ в v5.13 (замечание 1) // - integration_message: РЕШЕНИЕ — обмен с NATS завязан только на recognition_job (status queued→published = outbox; result_msg_id UNIQUE = inbox/дедуп); отдельная таблица появится, только если возникнет второй тип сообщений // - osv_entry_edit: история правок сумм ОСВ — audit_event(RECOGNIZED_DATA_EDIT, before/after) // - document_check: результат проверки периода/контрагента — две колонки (с v5.13 — в document) // - vgo_match: строковое сопоставление 1:1 — только в аналитике, нет ни в ТЗ (агрегат по Прил. 3), ни в UI; детализация агрегата — consolidated_osv_source // - export_preset: сохранённые пресеты колонок не в ТЗ/UI (выбор столбцов — при экспорте, без сохранения) // - export_log: история выгрузок (ТЗ 8.2, ЕХ 6.6) — audit_event(EXPORT) с составом в after_value // ============ ENUMS ============ Enum entity_type { "ЮЛ" "ИП" } Enum related_group_kind { gsk gsz } Enum account_kind { A [note: 'актив'] P [note: 'пассив'] AP [note: 'активно-пассивный'] } Enum account_row_type { account [note: 'синтетический счёт, напр. 60'] subaccount [note: 'субсчёт, напр. 60.01'] analytic [note: 'подсубсчёт, напр. 60.01.1 / 76.07.1'] } Enum business_segment { DKB [note: 'крупный бизнес'] DMSB [note: 'малый и средний'] DM [note: 'микробизнес'] } Enum document_type { BALANCE [note: 'Бухгалтерский баланс (0710001)'] INCOME [note: 'ОФР (0710002)'] OSV [note: 'ОСВ по счёту — osv_account_code обязателен'] DISCLOSURE [note: 'Расшифровка'] OTHER [note: 'Прочий документ'] } Enum file_type { XLS XLSX XLSB PDF PNG JPG } Enum recognition_pipeline { tabular [note: 'XLS/XLSX/XLSB — tabular-parser'] image [note: 'PDF/PNG/JPG — ИС Сканирование через scan-gateway'] } Enum period_quarter { Q1 Q2 Q3 Q4 } // v5.13: статус ФАЙЛА — только техническая обработка. Проверка и подтверждение аналитиком — на document. Enum file_status { UPLOADED [note: 'Загружен: поступил, в очереди'] PROCESSING [note: 'Обработка: распознавание/парсинг'] PROCESSED [note: 'Результат получен, документы созданы'] ERROR [note: 'Ошибка: нечитаемый файл, требуется повторная загрузка'] SUPERSEDED [note: 'Заменён новой версией файла (ТЗ 8.2) — вместе со всеми документами в нём'] } // v5.13: статус ДОКУМЕНТА (ANL-003: «Требуется проверка» при confidence < 100% или непройденных проверках; // «Подтвержден» — финальный после проверки аналитиком) Enum document_status { RECOGNIZED [note: 'данные получены, ручная проверка не требуется по порогу'] NEEDS_CHECK [note: 'Требуется ручная проверка: confidence < 100% / контрольные суммы / период или контрагент не совпали'] CONFIRMED [note: 'Подтвержден аналитиком: данные зафиксированы, доступны консолидации'] ERROR [note: 'документ не разобран (напр. не найдена шапка ОСВ)'] } Enum s3_bucket_role { uploads results exports archive } Enum recognition_status { queued [note: 'создано, запрос в outbox'] published [note: 'запрос опубликован в NATS'] processing [note: 'сервис подтвердил взятие в работу (опц. heartbeat)'] done needs_check [note: 'результат получен, но confidence < 100% / контрольные суммы не сошлись'] error timeout [note: 'reply_deadline истёк без результата'] dlq [note: 'сообщение ушло в DLQ после max_deliver'] } Enum statement_type { balance income } Enum osv_group_type { ACCOUNT DEPARTMENT COUNTERPARTY CONTRACT OTHER } Enum counterparty_match_kind { INN [note: 'автоматический резолв при маппинге: ИНН строки совпал с legal_entity.inn'] NAME [note: 'автоматический резолв: точное совпадение имени строки с legal_entity.name'] ALIAS [note: 'автоматический резолв: нормализованное имя ЮЛ или алиас (legal_entity_alias)'] MANUAL [note: 'ручное сопоставление аналитиком'] PROPAGATED [note: 'автоприменение ручного сопоставления к строкам с тем же именем в версии (DOC-221278 п. 12–13)'] } Enum osv_data_type { BU NU [note: 'v5.15: налоговый учёт из ОСВ «БУ, НУ» (OQ-45)'] CURRENCY QUANTITY SUM } Enum consolidation_scenario { CREATE [note: '«Создание КФО» при отсутствии клиентской консолидации'] CONFIRM [note: '«Подтверждение КФО клиента»'] } Enum consolidation_approach { BASIC [note: 'базовый: ББ, ОФР, ОСВ 01,58,60,62,66,67,76,90'] EXTENDED [note: 'опция к базовому: + ОСВ 02,03,08,44,26,91,51,52,59,80,83,86,97,001,008,009,011'] } // Статусы КФО по ТЗ 6.10 + «Корректировка» при расхождении ≥5% (ТЗ 6.4) Enum consolidation_status { INITIATED DOCUMENTS_PROCESSING DATA_CORRECTION [note: 'корректировка распознанных данных'] CONSOLIDATION_RF ADJUSTMENT [note: '«Корректировка» ВГО (расхождение ≥ 5%); подстатус этапа консолидации'] VERIFICATION_RF CONSOLIDATION_GO VERIFICATION_GO [note: 'РКА ГО, если КФО инициировано КА ГО (ТЗ 6.10 маршрут)'] VERIFIED CANCELLED // v5.1 по макету — ANL-035: подтвердить с заказчиком REJECTED [note: 'Отклонено: возвращено на доработку (макет). Альтернатива — событие в status_history без отдельного статуса'] ARCHIVED [note: 'Архив: скрыто из основного списка (макет); применимо к VERIFIED/CANCELLED'] } Enum correction_status { DRAFT APPLIED REVOKED } Enum document_check_result { MATCH MISMATCH UNKNOWN } Enum le_history_event { CREATE UPDATE MOVE_TO_GROUP [note: '«Переход в другую ГК»'] CLOSE [note: '«Закрытие» / вывод из состава ГК'] REMOVE_FROM_GROUP } Enum ex_snapshot_status { ACTUAL ARCHIVE } // v5.13: объекты и действия матрицы прав (ТЗ Прил. 2 «Роль: перечень полномочий», сноска 29) Enum permission_object { DIRECTORY [note: 'справочники ГК/ГСК/ГСЗ/ЮЛ'] DOCUMENT [note: 'первичные документы и распознанные данные'] CONSOLIDATION [note: 'консолидация, версии, корректировки, гудвилл'] KFO [note: 'результат КФО: утверждение, верификация'] EX [note: 'Единое хранилище'] EXPORT USERS [note: 'пользователи и роли'] AUDIT [note: 'журнал действий'] } Enum permission_action { VIEW EDIT APPROVE [note: 'утверждение в РФ'] VERIFY [note: 'верификация в ГО'] DELETE EXPORT ADMIN } Enum audit_section { DIRECTORIES CONSOLIDATION PRIMARY_DOCUMENTS RECOGNITION KFO EXPORT ACCESS SESSION USERS } Enum audit_action { LOGIN LOGIN_FAILED LOGOUT CONSOLIDATION_CREATE CONSOLIDATION_STATUS_CHANGE CONSOLIDATION_APPROVE CONSOLIDATION_VERIFY GROUP_CHANGE LEGAL_ENTITY_CHANGE [note: 'v5.14: ЮЛ создано/изменено/закрыто/переведено, алиасы'] MEMBERSHIP_CHANGE ASSIGNMENT_CHANGE FILE_UPLOAD FILE_DELETE FILE_REPLACE DOCUMENT_CONFIRM CONSOLIDATION_LOCK CONSOLIDATION_LOCK_TAKEOVER DISCLOSURE_EDIT RECOGNIZED_DATA_EDIT OSV_COUNTERPARTY_MAP OSV_TYPE_OVERRIDE CORRECTION_CREATE CORRECTION_EDIT CORRECTION_REVOKE GOODWILL_EDIT EXPORT DIRECTORY_IMPORT USER_ROLE_CHANGE USER_CHANGE [note: 'v5.14: пользователь создан/изменён/деактивирован администратором'] ROLE_PERMISSION_CHANGE } // ============ СПРАВОЧНИКИ ============ // ИСТОЧНИК: // ТЗ: ТЗ 8.1 (пользователь ГО / пользователь РФ; закрепление ГК за РФ); Прил. 2 «Пользователь: привязка к РФ/ГО» // Аналитика: md «Модель данных» (regional_office) // Макет: справочник ГК: колонка «Основной РФ»; пользователи: «Ростовский РФ» и т.п. // В модели: v4 // Зачем: справочник филиалов; DECISION-3 — ГО явной записью Table regional_office { id uuid [pk] name varchar [not null, unique] is_head_office boolean [not null, default: false, note: 'DECISION-3: ГО — явная запись'] is_active boolean [not null, default: true] Note: 'Филиал: ГО и региональные филиалы (РФ)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.1 (справочник ГК, импорт из ЕЦП CRM, создание вручную Куратором); Прил. 2 «ГК: id, наименование, id ЕЦП CRM» // Аналитика: md «Модель данных» (company_group, SCD4) // Макет: «Группы компаний»: Создать ГК, CRM ID «генерируется автоматически», Архив // В модели: v4 + v5.1 (code, crm_id nullable — ANL-037) Table company_group { id uuid [pk] code varchar [not null, unique, note: 'v5.1 ANL-037: внутренний код ГК, генерируется системой'] crm_id varchar [unique, note: 'ключ из ЕЦП CRM; NULL для ГК, созданной вручную Куратором до сверки с CRM'] name varchar [not null] base_regional_office_id uuid [ref: > regional_office.id, note: 'закрепление ГК за филиалом (ТЗ 6.1/8.1); перезакрепление Куратором = UPDATE + снимок в history'] is_archived boolean [not null, default: false, note: 'v5.1 макет: «Архив» ГК'] valid_from timestamptz [not null] updated_by uuid [ref: > app_user.id] created_at timestamptz updated_at timestamptz Note: 'ГК — ТЕКУЩЕЕ состояние (SCD4). История — company_group_history' } // ИСТОЧНИК: // ТЗ: ТЗ 6.10 «фиксация изменений ГК/ГСК в истории изменений»; 8.2 «изменение состава ГСК, ГК» // Аналитика: md «Модель данных» (company_group_history) // Макет: «История изменений» в карточке ГК // В модели: v4 // Зачем: прошлые снимки ГК (SCD4) Table company_group_history { id uuid [pk] company_group_id uuid [not null, ref: > company_group.id] crm_id varchar name varchar base_regional_office_id uuid valid_from timestamptz valid_to timestamptz changed_by uuid [ref: > app_user.id] Indexes { (company_group_id, valid_from) [unique] } } // ИСТОЧНИК: // ТЗ: ТЗ 6.1 (ЮЛ/ИП, только одна ГСК/ГСЗ); Прил. 2 «ЮЛ: id, наименование, ИНН, id CRM, ГК, признак»; ЕХ ТЗ 6.1 «Монозаёмщик» // Аналитика: md «Модель данных» (legal_entity, SCD4) // Макет: «Юридические лица», «ЮЛ вне ГК» / «Монозаемщики» // В модели: v4 + v5 (crm_id, is_mono_borrower, ГК nullable); v5.17 — kpp Table legal_entity { id uuid [pk] inn varchar [not null, unique] crm_id varchar [unique, note: 'ADD: id ЮЛ в ЕЦП CRM (Прил. 2 ТЗ)'] entity_type entity_type name varchar [not null] company_group_id uuid [ref: > company_group.id, note: 'nullable: NULL = монозаёмщик (ЕХ 6.1), признак выводится'] regional_office_id uuid [ref: > regional_office.id] close_date date [note: 'NULL = активна'] valid_from timestamptz [not null] updated_by uuid [ref: > app_user.id] created_at timestamptz updated_at timestamptz kpp varchar [note: 'v5.17 (добавлена последней колонкой, как её добавляет миграция 0005): КПП, nullable; 9 символов (4 цифры, 2 цифры/латинские A–Z, 3 цифры); у ИП запрещён — проверяет ядро (INVALID_KPP). Источник: макет [50][51-02][61][79], решение владельца 07.10.2026 (OQ-31)'] Note: 'Организация (ЮЛ/ИП) — ТЕКУЩЕЕ состояние (SCD4). История — legal_entity_history' } // ИСТОЧНИК: // ТЗ: ТЗ 8.2 (журнал изменений справочников); 6.10 (история изменений состава) // Аналитика: md «Модель данных» (legal_entity_history) // Макет: Архив ГК: события «Переход в другую ГК», «Закрытие», дата перевода // В модели: v4 + v5.1 (event_type, effective_date — ANL-046); v5.17 — kpp Table legal_entity_history { id uuid [pk] legal_entity_id uuid [not null, ref: > legal_entity.id] inn varchar crm_id varchar entity_type entity_type name varchar company_group_id uuid regional_office_id uuid close_date date event_type le_history_event [note: 'v5.1 макет: «Переход в другую ГК» / «Закрытие» — для архива ГК'] effective_date date [note: 'дата перевода/закрытия (не раньше текущей — ANL-046)'] valid_from timestamptz valid_to timestamptz changed_by uuid [ref: > app_user.id] kpp varchar [note: 'v5.17 (последней колонкой, миграция 0005): снимок КПП на момент события'] Indexes { (legal_entity_id, valid_from) [unique] } } // ИСТОЧНИК: // ТЗ: ТЗ 6.5 «ведение справочника соответствий наименований контрагентов», «использование ранее подтверждённых сопоставлений» // Аналитика: md «Сводная ОСВ» (алгоритм ручного сопоставления, legal_entity_alias); FNR01 п.9 // Макет: карточка ЮЛ: «Альтернативные наименования», «Связь с компанией из ОСВ (алиас)» // В модели: v4 Table legal_entity_alias { id uuid [pk] legal_entity_id uuid [not null, ref: > legal_entity.id] alias varchar [not null, note: 'как в ОСВ'] normalized_alias varchar [not null, note: 'lower/trim/без пунктуации/без ОПФ'] created_by uuid [ref: > app_user.id] created_at timestamptz Indexes { (legal_entity_id, normalized_alias) [unique] normalized_alias [note: 'ANL-028: конфликт «один алиас у разных ЮЛ» решается при ручном сопоставлении'] } Note: 'Словарь соответствий наименований контрагентов (ТЗ 6.5)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.1 (ГСК/ГСЗ в составе ГК; иерархия не поддерживается; в одной ГК несколько ГСК — сноска 11); Прил. 2 «ГСК/ГСЗ» // Аналитика: md «Модель данных» (related_group, kind) // Макет: вкладки «ГСК», «ГСЗ», коды GSK-001/GSZ-001 // В модели: v4 Table related_group { id uuid [pk] code varchar [not null, unique] company_group_id uuid [not null, ref: > company_group.id] kind related_group_kind [not null] name varchar [not null] created_date date Note: 'ГСК/ГСЗ — current-state. Иерархия групп не поддерживается (ТЗ 6.1)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.1 «КА может редактировать состав ГСК»; «одно ЮЛ только в одной ГСК или ГСЗ» // Аналитика: md «Модель данных» (related_group_member, SCD2) // Макет: «Состав», «Входит в ГСК / ГСЗ», «Редактировать состав» // В модели: v4 (индекс уточнён — ANL-030); v5.14 — kind и индекс (legal_entity_id, kind) Table related_group_member { id uuid [pk] related_group_id uuid [not null, ref: > related_group.id] legal_entity_id uuid [not null, ref: > legal_entity.id] kind related_group_kind [not null, note: 'v5.14: денормализация related_group.kind ради частичного уникального индекса; равенство с группой проверяет ядро при записи'] valid_from timestamptz [not null] valid_to timestamptz is_current boolean [not null] created_by uuid [ref: > app_user.id] Indexes { (related_group_id, legal_entity_id) [note: 'partial UNIQUE WHERE is_current'] (legal_entity_id, kind) [note: 'partial UNIQUE WHERE is_current — v5.14: ЮЛ в одной ГСК И одной ГСЗ одновременно (решение владельца 04.10.2026, отменяет ANL-030); ГК у ЮЛ и у группы должна совпадать (проверяет ядро)'] } Note: 'Состав ГСК/ГСЗ — SCD2. Исключение = закрытие версии (valid_to, is_current=false)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2/6.5 (счета и субсчета 01…90, нормализация 60.1=60.01); Прил. 3/4 (пары счетов); сноска 32 // Аналитика: md «Модель данных» (chart_of_account); md «Шахматка» (kind A/P/AP, row_type) // Макет: фильтр «Дебетовые/Кредитовые счета», полный план счетов 1С в бандле // В модели: v4 + v5 (row_type, is_off_balance) Table chart_of_account { id uuid [pk] code varchar [not null, unique, note: 'нормализованный код: 60.01, 76.07.1'] parent_id uuid [ref: > chart_of_account.id] name varchar [not null] kind account_kind row_type account_row_type [not null, note: 'FIX: используется в «Шахматке»'] is_off_balance boolean [not null, default: false, note: '001, 008, 009, 011'] Note: 'План счетов' } // ADD ТЗ Прил. 3/4 как данные (сноски 32/35: пары могут корректироваться на этапе реализации) // ИСТОЧНИК: // ТЗ: ТЗ 6.4 + Прил. 3 «Перечень статей ББ» и Прил. 4 «Модель расчётов ОФР»; сноски 32/35 «могут быть скорректированы на этапе реализации» // Аналитика: md «Автоматическое сопоставление ВГО» (таблица «Правила встречных счетов» — хардкод) // Макет: — // В модели: v5 новая // Зачем: правила элиминации как данные, версионируемые Table elimination_rule { id uuid [pk] statement_type statement_type [not null, note: 'balance = Прил. 3, income = Прил. 4'] rule_no smallint [not null, note: '№ строки приложения'] article_1_line varchar [not null, note: 'статья Компании 1, код строки ББ/ОФР, напр. 1230'] article_2_line varchar [not null, note: 'статья Компании 2, напр. 1520'] side_1 varchar [not null, note: 'D | K — сторона Компании 1'] measure varchar [not null, note: 'closing | turnover — сальдо на конец (ББ) или оборот (ОФР)'] elimination_mode varchar [not null, note: 'min | full | manual — min(A,B) / вся сумма ВГО (ОФР выручка) / вручную'] vat_handling varchar [note: 'none | check_90_01_vs_2110 | weighted_rate — только для ОФР'] excise_adjust boolean [not null, default: false] comment text version_no int [not null] valid_from date [not null] valid_to date Indexes { (statement_type, rule_no, version_no) [unique] } Note: 'Правило элиминации. Состав счетов — в elimination_rule_account' } // ИСТОЧНИК: // ТЗ: ТЗ Прил. 3/4 (столбцы «Пары счетов Компании 1 и Компании 2», исключения «76 кроме 76.7.1») // Аналитика: md «Автоматическое сопоставление ВГО» // Макет: — // В модели: v5 новая // Зачем: состав счетов сторон правила Table elimination_rule_account { id uuid [pk] rule_id uuid [not null, ref: > elimination_rule.id] company_no smallint [not null, note: '1 или 2'] account_id uuid [not null, ref: > chart_of_account.id] is_excluded boolean [not null, default: false, note: 'true = исключение внутри маски (напр. «76 кроме 76.7.1»)'] Note: 'Счета/субсчета сторон правила' } // ============ ПОЛЬЗОВАТЕЛИ И ДОСТУП ============ // ИСТОЧНИК: // ТЗ: ТЗ 8.1 (роли, ГО/РФ, ЗО); Прил. 2 «Пользователь: ФИО, роль, привязка к РФ/ГО, ЗО, табельный номер»; РАИСА // Аналитика: md «Аутентификация и авторизация» (users: email, role, JWT РАИСА); md «Модель данных» (app_user) // Макет: «Пользователи»: Логин, Роль, Активен, Деактивация // В модели: v4 + v5 (employee_no, business_segment) Table app_user { id uuid [pk] external_id varchar [unique, note: 'субъект РАИСА/SSO; аутентификация внешняя (ANL-018)'] email varchar [unique, note: 'v5.15: хранится в нижнем регистре (нормализация при записи), сравнение по lower(email) (OQ-59)'] full_name varchar [not null] employee_no varchar [note: 'ADD: табельный номер (Прил. 2 ТЗ)'] regional_office_id uuid [not null, ref: > regional_office.id, note: 'ГО или РФ (DECISION-3)'] business_segment business_segment [note: 'ADD: зона ответственности ДКБ/ДМСБ/ДМ (ТЗ 8.1)'] is_active boolean [not null, default: true] created_at timestamptz updated_at timestamptz } // ИСТОЧНИК: // ТЗ: ТЗ 8.1 таблица ролей (КА РФ, КА ГО, РКА, КМ, Куратор, Эксперт, Администратор); Прил. 2 «Роль» // Аналитика: md «Ролевая модель»; md «Модель данных» (role.code) // Макет: «Изменение ролей и прав доступа» (Аналитик ДКБ, Наблюдатель… — расходится с ТЗ, ANL-034) // В модели: v4 (коды → ТЗ в v5); v5.13 — матрица полномочий в role_permission Table role { id uuid [pk] code varchar [not null, unique, note: 'ТЗ 8.1: KA_RF | KA_GO | RKA | CLIENT_MANAGER | CURATOR_GO | EXPERT | ADMIN. Seed; администратор может заводить дополнительные роли (замена исключениям по пользователю)'] name varchar [not null] description text is_system boolean [not null, default: false, note: 'роли ТЗ 8.1 — не удаляются'] is_active boolean [not null, default: true] Note: 'Роль. Полномочия — через role_permission (v5.13); код роли в бизнес-логике больше не проверяется' } // ИСТОЧНИК: // ТЗ: ТЗ Прил. 2 «Роль: перечень полномочий»; сноска 29 «матрица доступов будет разработана» // Аналитика: md «Ролевая модель» // Макет: карточка роли: «Права: Просмотр, редактирование КФО / справочники / консолидации» // В модели: v5 новая, убрана в v5.3/v5.4, ВОЗВРАЩЕНА в v5.13 (замечание 1) // Зачем: атомарное право «объект × действие»; seed, администратор новые коды не создаёт Table permission { id uuid [pk] code varchar [not null, unique, note: 'напр. CONSOLIDATION_EDIT, KFO_VERIFY, DIRECTORY_EDIT, USERS_ADMIN'] object_type permission_object [not null] action permission_action [not null] name varchar [not null] description text Indexes { (object_type, action) [unique] } Note: 'Справочник прав (seed, привязан к коду). Ограничения по ЗО/филиалу и статусу КФО — отдельный policy-слой поверх прав: право говорит «может», policy — «эту ли и сейчас ли»' } // ИСТОЧНИК: // ТЗ: ТЗ Прил. 2 «Роль: перечень полномочий» // Аналитика: нет // Макет: «Права» в карточке роли, «Изменение ролей и прав доступа» // В модели: v5.13 (возвращена) // Зачем: матрица доступов как данные; редактируется администратором, история — audit_event(ROLE_PERMISSION_CHANGE) Table role_permission { role_id uuid [not null, ref: > role.id] permission_id uuid [not null, ref: > permission.id] granted_by uuid [ref: > app_user.id] granted_at timestamptz Indexes { (role_id, permission_id) [pk] } Note: 'effective_permissions(user) = ∪ permissions(roles(user)). Прямых прав у пользователя нет (решение 30.09): исключения — через заведение роли' } // ИСТОЧНИК: // ТЗ: ТЗ 8.1 (роль пользователя); Прил. 2 «Пользователь — Роль: многие-к-одному» // Аналитика: md «Модель данных» v4 (user_role M:N) // Макет: «Роль» пользователя // В модели: v4 // Зачем: M:N оставлен (пользователь может быть КА и Куратором); если ТЗ строго 1:1 — заменить на app_user.role_id Table user_role { id uuid [pk] user_id uuid [not null, ref: > app_user.id] role_id uuid [not null, ref: > role.id] Indexes { (user_id, role_id) [unique] } Note: 'M:N current-state. История назначений — audit_event(USER_ROLE_CHANGE)' } // ============ ФАЙЛЫ / РАСПОЗНАВАНИЕ ============ // ИСТОЧНИК: // ТЗ: 6.2 (файлы с префиксом UUID, исходные документы хранятся); 8.4 «хранение 5 лет с даты прекращения отношений, архивирование/удаление»; ЕХ 6.2 «ссылка на исходные документы с возможностью скачать», ЕХ 8.3 «архивирование в data lake» // Аналитика: md «Модель данных» v4 (s3_key, raw_result_s3_key — строки) // Макет: — // В модели: v5.2 новая (DECISION-4), подтверждена в v5.4 // Зачем: единая точка ключей всех бакетов для lifecycle/ретеншна и дедупа; без реестра ключи размазаны по 4 таблицам Table s3_object { id uuid [pk] bucket_role s3_bucket_role [not null] bucket varchar [not null] key varchar [not null, note: 'схема ключа: {file_id}/{job_id}/{name}'] content_type varchar size_bytes bigint sha256 varchar version_id varchar [note: 'S3 object version при включённом versioning'] created_by varchar [note: 'arm-core | tabular-parser | scan-gateway | user:{id}'] created_at timestamptz [not null] expires_at timestamptz [note: 'lifecycle: NULL = весь срок (ТЗ 8.4); exports/ = 7 дней; results/ — решение ARCH-010'] archived_to varchar [note: 'ссылка в data lake после архивирования (ЕХ 8.3)'] Indexes { (bucket, key) [unique] } Note: 'Реестр объектов S3. stored_file / recognition_job ссылаются сюда FK' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 (типы и форматы документов, UUID); 8.2 «первичные документы не редактируются, только замена с версионностью» // Аналитика: md «Загрузка документов» (панель, UC-01…05); FNR01 «Маппинг stored_file» // Макет: «Первичные документы», «Загрузка документов», «Замена документа», «Удаление документа» // В модели: v4 + v5 (version_no, superseded_by) + v5.2 (s3_object_id); v5.13 — только ФИЗИЧЕСКИЙ файл, документы → document; v5.18 — consolidation_id Table stored_file { id uuid [pk] legal_entity_id uuid [not null, ref: > legal_entity.id, note: 'организация-владелец (выбрана при загрузке)'] file_type file_type [not null] original_name varchar s3_object_id uuid [not null, ref: > s3_object.id, note: 'исходный файл в uploads'] content_hash varchar [note: 'sha256, дедуп (UC-01 A2)'] size_bytes bigint status file_status [not null] version_no int [not null, default: 1, note: 'ADD ТЗ 8.2: версия при замене'] superseded_by_file_id uuid [ref: > stored_file.id, note: 'ссылка на файл-замену; статус SUPERSEDED. Замена — файл ЦЕЛИКОМ со всеми документами в нём (решение 01.10)'] current_recognition_job_id uuid [ref: > recognition_job.id, note: 'авторитетный прогон; его результат породил актуальные document'] uploaded_by uuid [ref: > app_user.id] uploaded_at timestamptz updated_at timestamptz consolidation_id uuid [ref: > consolidation.id, note: 'v5.18: консолидация (период ГК), в контексте которой загружен файл — основа «рабочего стола консоли» (полный состав — context/05-domain-rules.md §9.5 п. 11), общего для версий периода; NULL — без контекста. Пишется при загрузке; при автозамене файл без контекста наследует значение заменённого'] Indexes { (legal_entity_id, status) (legal_entity_id, consolidation_id) [note: 'v5.18: основа рабочего стола консоли для ЮЛ (§9.5 п. 11)'] current_recognition_job_id [note: 'v5.15: проверка FK при удалении recognition_job / файла (OQ-117)'] } Note: 'v5.13: физический файл одной загрузки (S3 — только ссылка). Что внутри — document (1:N). Первичка не редактируется. Удаление физическое запрещено после INITIATED — только SUPERSEDED (ANL-020). v5.18: consolidation_id — где загружен; что использует версия — consolidation_document' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 (типы документов: ББ, ОФР, ОСВ по счетам; обязательный набор); 5.1 (подход); 8.2; Прил. 2 «Документ ФО» // Аналитика: md «Загрузка документов» (чек-лист, UC-01 A1 период, «Подтвердить данные»); md «Парсинг ОСВ» (счёт/период из шапки) // Макет: «Чек-лист загруженных документов», «Соответствует период», «Не соответствует контрагент», «Подтвердить данные» // В модели: v5.13 новая (замечание 6-бис) // Зачем: один файл = N документов (PDF ББ+ОФР; xlsx с несколькими ОСВ). Чек-лист, проверки, подтверждение и // FK разобранных данных — здесь; без этого «ОСВ 58 загружена?» по файлу не ответить Table document { id uuid [pk] stored_file_id uuid [not null, ref: > stored_file.id] legal_entity_id uuid [not null, ref: > legal_entity.id, note: 'денормализация stored_file.legal_entity_id — выборки чек-листа по ЮЛ+период'] document_type document_type [not null] osv_account_code varchar [note: 'для OSV — счёт по шапке (01, 60, 62…), нормализованный; NULL для прочих типов'] period_year smallint [note: 'из шапки/формы'] period_quarter period_quarter is_cumulative boolean [note: 'true = нарастающий итог с начала года'] reporting_date date [note: 'отчётная дата, если определена'] locator varchar [note: 'где в файле: лист xlsx / диапазон страниц PDF (напр. «sheet:ОСВ 58», «pages:1-2»)'] status document_status [not null] period_check document_check_result [note: 'UC-01 A1 / макет «Соответствует период»'] counterparty_check document_check_result [note: 'макет «Не соответствует контрагент» — ИНН/наименование из шапки vs legal_entity'] confirmed_by uuid [ref: > app_user.id, note: 'кто нажал «Подтвердить данные» → audit_event(DOCUMENT_CONFIRM)'] confirmed_at timestamptz created_at timestamptz updated_at timestamptz Indexes { (stored_file_id, document_type, osv_account_code) [unique, note: 'NULLS NOT DISTINCT — один ББ / один ОФР / одна ОСВ по счёту на файл'] (legal_entity_id, document_type, osv_account_code, period_year, period_quarter, status) [note: 'чек-лист required_document_rule'] } Note: 'Логический документ внутри файла. Создаётся ядром при маппинге результата recognition_job. Разобранные данные: OSV → osv_entry; BALANCE/INCOME → financial_statement_meta. Счётчики «исправлено полей» — COUNT(is_modified) по данным, не хранятся' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 (статусы процесса распознавания, ИС Сканирование); 6.10 «повторная обработка»; 7.1 // Аналитика: FNR01 «Маппинг recognition_job»; md «Загрузка документов» UC-02; md «Парсинг ОСВ» (схема группировок шапки) // Макет: «Система распознавания: получен ответ», «Обработка», «Ошибка» // В модели: v4, переписана в v5.2 под контракт NATS (DECISION-4); v5.13 — group_schema jsonb, убран raw_result Table recognition_job { id uuid [pk, note: 'job_id в контракте NATS'] file_id uuid [not null, ref: > stored_file.id] pipeline recognition_pipeline [not null, note: 'tabular → tabular-parser; image → scan-gateway'] request_subject varchar [not null, note: 'recognition.tabular.request | recognition.image.request'] request_msg_id varchar [unique, note: 'Nats-Msg-Id запроса (outbox)'] result_msg_id varchar [unique, note: 'Nats-Msg-Id результата (inbox, дедуп)'] schema_version varchar [not null, note: 'версия контракта сообщений'] service_name varchar [note: 'tabular-parser | scan-gateway — из результата'] service_version varchar status recognition_status [not null] attempt smallint [not null, default: 1] max_attempts smallint [not null, default: 1, note: 'OCR: 1 (0 повторов), парсер: 3'] requested_at timestamptz published_at timestamptz started_at timestamptz finished_at timestamptz reply_deadline timestamptz [note: 'таймаут ожидания (OCR — 20 мин)'] external_task_id varchar [note: 'id задания в ИС Сканирование (только image)'] error_code varchar [note: 'UNSUPPORTED_FORMAT | UNREADABLE | TIMEOUT | SCHEMA_MISMATCH | …'] error_message text result_s3_object_id uuid [ref: > s3_object.id, note: 'нормализованный JSON в results — единственная ссылка на результат (v5.13). Сырой ответ ИС Сканирование — S3 raw/{job_id}.json по конвенции, в БД не регистрируется'] result_schema_version varchar [note: 'версия схемы JSON результата (ARCH-008/009)'] group_schema jsonb [note: 'v5.13 (вместо osv_group_schema): схема группировок шапки ОСВ по листам — [{\"locator\":\"sheet:ОСВ 58\",\"groups\":[{\"type\":\"ACCOUNT\"},{\"type\":\"COUNTERPARTY\",\"has_inn\":true},{\"type\":\"CONTRACT\"}]}]; только для tabular'] metrics jsonb [note: 'duration_ms, pages, rows, warnings'] fields_total int [note: 'v5.7: всего извлечённых значений (ячейки формы / строки ОСВ)'] fields_low_confidence int [note: 'v5.7: значений с confidence < 100 (ТЗ 6.2) — заполняется при маппинге результата'] fields_unrecognized int [note: 'v5.7: значений с is_recognized = false'] Indexes { (file_id, attempt) [unique] } Note: 'РЕШЕНИЕ v5.4: единственная точка обмена с NATS. Запись = outbox: создаётся в queued в одной транзакции с stored_file, фоновый публикатор переводит в published (при сбое публикации — повтор по queued старше N сек); результат приходит по NATS, дедуп по result_msg_id UNIQUE; redelivery после max_deliver → status=dlq, replay = сброс в queued с attempt+1. Ядро маппит JSON из S3 в document → osv_entry / financial_statement_*' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 (Osv_report_lines); 6.5 (строка ОСВ: компания, счёт, контрагент, признак, 6 сумм); Прил. 2 «Строка сводной ОСВ», «Распознанный показатель» // Аналитика: FNR01 «Маппинг osv_entry»; md «Парсинг ОСВ» п.11 (row_number, level, group_type, data_type) // Макет: «Оборотно-сальдовая ведомость»: Дебет/Кредит начало/оборот/конец, «Тип контрагента» // В модели: v4 + v5 (owner_legal_entity_id — FIX, parent_entry_id, is_modified, is_excisable); v5.13 — document_id, group_type вместо group_schema_id; v5.16 — counterparty_match_kind Table osv_entry { id uuid [pk] recognition_job_id uuid [not null, ref: > recognition_job.id] document_id uuid [not null, ref: > document.id, note: 'v5.13: ОСВ-документ (счёт + период); файл — через document.stored_file_id'] owner_legal_entity_id uuid [not null, ref: > legal_entity.id, note: 'FIX: организация-ВЛАДЕЛЕЦ ОСВ (денормализация). В v4 называлось legal_entity_id с ошибочной note'] parent_entry_id uuid [ref: > osv_entry.id, note: 'родитель в дереве строк ОСВ (ТЗ 6.5). ОТКРЫТО (замечание 2, отложено 01.10): хранить ли только контрагентские строки и нужно ли дерево'] group_type osv_group_type [note: 'v5.13: тип строки по схеме шапки (recognition_job.group_schema) — ACCOUNT / DEPARTMENT / COUNTERPARTY / CONTRACT / OTHER'] group_name varchar [note: 'исходное название группировки из шапки («Контрагенты», «Договоры»)'] row_number int level smallint row_value varchar [note: 'исходный текст строки'] data_type osv_data_type account_code varchar [note: 'сырой код счёта'] account_id uuid [ref: > chart_of_account.id] account_root varchar counterparty_name varchar [note: 'сырое имя из ОСВ, не изменяется'] counterparty_inn varchar [note: 'сырой ИНН, не изменяется'] counterparty_id uuid [ref: > legal_entity.id, note: 'резолв по ИНН/имени/алиасу или ручное сопоставление; NULL = не сопоставлен. Признак ГСК/ГК/Внешний выводится из периметра версии (DECISION-5)'] opening_debit numeric(18,2) opening_credit numeric(18,2) turnover_debit numeric(18,2) turnover_credit numeric(18,2) closing_debit numeric(18,2) closing_credit numeric(18,2) is_modified boolean [not null, default: false, note: 'значения правились аналитиком'] original_values jsonb [note: 'v5.6: суммы до первой ручной правки {opening_debit,…}; NULL = не правилось'] modified_by uuid [ref: > app_user.id] modified_at timestamptz is_excisable boolean [note: 'ADD ТЗ 6.4: признак «подакцизный товар» для ОСВ 90'] counterparty_match_kind counterparty_match_kind [note: 'v5.16: NULL ⇔ counterparty_id NULL; INN/NAME/ALIAS — шаг автоматического резолва при маппинге; MANUAL — ручное сопоставление; PROPAGATED — автоприменение по имени в версии (DOC-221278 п. 12–13)'] Indexes { (document_id, row_number) [unique] (owner_legal_entity_id, account_id, counterparty_id) parent_entry_id [note: 'v5.15: обход дерева строк и проверка self-FK при удалении документа (OQ-117)'] recognition_job_id [note: 'v5.15: проверка FK при удалении recognition_job / файла (OQ-117)'] } Note: 'Строка ОСВ. Правки сумм аналитиком — original_values/modified_by + audit_event(RECOGNIZED_DATA_EDIT, before/after)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 «извлекаются значения по кодам форм Приказа 157н»; 6.3 проверки единиц измерения/даты // Аналитика: FNR01 «fin_report_header» (report_type, org, unit, currency, reporting_date); md «Модель данных» v4 // Макет: «Реквизиты организации» (ОКПО, ОКОПФ, ОКФС, ОКУД, ОКЕИ, адрес), «Единица измерения», «Отчетная дата» // В модели: v4 (FIX pk) + v5.1 (реквизиты — ANL-044); v5.13 — document_id Table financial_statement_meta { id uuid [pk, note: 'FIX: был без PK'] document_id uuid [not null, unique, ref: > document.id, note: 'v5.13: документ BALANCE/INCOME (1:1); файл — через document'] recognition_job_id uuid [not null, ref: > recognition_job.id] statement_type statement_type [not null] inn varchar inn_original_value varchar inn_confidence numeric(5,2) inn_verified boolean inn_modified boolean organization_name varchar [note: 'как в документе; сверяется с legal_entity/alias, при несовпадении — document.counterparty_check'] report_date date report_date_original_value varchar [note: 'FIX: строка как распознано (был `date []`)'] report_date_verified boolean report_date_modified boolean units varchar [note: 'тыс. руб. / руб.'] units_original_value varchar units_verified boolean units_modified boolean currency varchar [default: 'RUB'] okpo varchar [note: 'v5.1 ANL-044: реквизиты по макету экрана распознавания'] okopf varchar okfs varchar okud varchar [note: '0710001 / 0710002'] okei varchar legal_form varchar [note: 'ОПФ'] ownership_form varchar address varchar subject_to_audit boolean reviewed_by uuid [ref: > app_user.id, note: 'v5.9: кто проверил данные формы; подтверждение документа — document.confirmed_by'] reviewed_at timestamptz checks_accepted_by uuid [ref: > app_user.id, note: 'v5.9 ТЗ 6.3 п.7: акцепт КА непройденных проверок (напр. 1370/2400) с комментарием'] checks_accepted_at timestamptz checks_accepted_comment text Note: 'Шапка распознанной формы ББ/ОФР. Логические проверки (ТЗ 6.3 Таблица 1) не хранятся — считаются на лету по value' } // ИСТОЧНИК: // ТЗ: ТЗ 6.3 п.7 «стр. 1370 на конец и на начало года»; 6.7 «УК ДО на отчётную дату» // Аналитика: FNR01 «rows[0].cells[2..4] — наименования колонок периода»; md «Логические проверки» («по каждой отчётной дате (колонке)») // Макет: колонки «На 30 сентября 2025 г.», «На 31 декабря 2024 г.», «Выбор периодов» // В модели: v5 новая (FIX-14) // Зачем: дата колонки — без неё межформенная проверка и гудвилл не считаются Table financial_statement_column { id uuid [pk] meta_id uuid [not null, ref: > financial_statement_meta.id] column_index smallint [not null] label varchar [note: 'как в форме: «На 30 сентября 2025 г.», «За 9 месяцев 2025 г.»'] period_date date [note: 'ADD FIX-14: дата колонки — нужна для 1370(нач.года), гудвилла «на отчётную дату», сравнения версий'] is_current boolean [note: 'колонка отчётного периода'] Indexes { (meta_id, column_index) [unique] } Note: 'Колонки-периоды формы (2–3 сравнительные даты)' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 (коды статей по формам 157н); 6.3 (Таблица 1 — коды строк 1100…2400) // Аналитика: FNR01 «fin_report_rows» (row_name, row_code, section, is_total_row); md «Модель данных» v4 // Макет: «Результат распознавания»: Наименование показателя, Код, разделы // В модели: v4 (FIX: убраны копипаст-поля) Table financial_statement_line { id uuid [pk] meta_id uuid [not null, ref: > financial_statement_meta.id] line_code varchar [note: 'код строки ОКУД, напр. 1230'] line_code_original_value varchar line_code_confidence numeric(5,2) line_code_modified boolean line_name varchar line_name_original_value varchar line_name_confidence numeric(5,2) line_name_modified boolean [note: 'FIX: был numeric'] Indexes { (meta_id, line_code) [unique] } Note: 'Только строки с кодом; порядок, разделы и итоговые строки — шаблон формы ФСБУ 4/2023 на фронте' } // ИСТОЧНИК: // ТЗ: ТЗ 6.2 «уверенность распознавания ниже 100% → Требуется ручная проверка»; Прил. 2 «Распознанный показатель: значение, уверенность, признак ручной корректировки» // Аналитика: FNR01 (value_current/prior/prior2, confidence, скобки → отрицательное); md «Логические проверки» (правка ячеек) // Макет: подсветка ячеек, «не распознано», «Редактировать/Сохранить», «Подтвердить данные» // В модели: v4 + v5 (column_id, confidence, original_amount, modified_by); v5.13 — убран needs_review Table financial_statement_value { id uuid [pk] line_id uuid [not null, ref: > financial_statement_line.id] column_id uuid [not null, ref: > financial_statement_column.id] amount numeric(18,2) string_value varchar [note: 'как распознано, если не число'] confidence numeric(5,2) [note: 'уверенность распознавания (Прил. 2 ТЗ). Подсветка «требует проверки» = confidence < порог OR NOT is_recognized — считается, не хранится (v5.13)'] is_recognized boolean is_modified boolean [not null, default: false] original_amount numeric(18,2) [note: 'значение до ручной правки'] modified_by uuid [ref: > app_user.id] modified_at timestamptz Indexes { (line_id, column_id) [unique] } } // ============ КОНСОЛИДАЦИЯ ============ // ИСТОЧНИК: // ТЗ: ТЗ 6.10 (процесс формирования КФО за отчётный период); 7.2 «формируется по требованию»; Прил. 2 «Консолидация: ГСК, отчётный период» // Аналитика: md «Сводная ОСВ» / «Шахматка» (consolidation.year/quarter); md «Модель данных» v4 // Макет: «Создание консолидации: ГК → ГСК → Квартал» // В модели: v4 + v5 (reporting_date) // Зачем: логическая консолидация ГСК+период; версии — ниже Table consolidation { id uuid [pk] company_group_id uuid [not null, ref: > company_group.id, note: 'v5.9: консолидация создаётся от ГК'] related_group_id uuid [ref: > related_group.id, note: 'v5.9: ГСК — NULL до утверждения; фиксируется/создаётся при VERIFIED по составу consolidation_member (ANL-029)'] year smallint [not null] quarter period_quarter [not null] reporting_date date [not null, note: 'ADD: 01.04 / 01.07 / 01.10 / 01.01 след. года — для ЕХ и сравнений'] created_at timestamptz Indexes { (company_group_id, year, quarter) [unique] } Note: 'Логическая консолидация ГК за период; версии различаются периметром (consolidation_member). Прошлые периоды — по company_group_id. v5.18: файлы, загруженные в консолидацию, — stored_file.consolidation_id (основа рабочего стола; полный состав — context/05-domain-rules.md §9.5 п. 11)' } // ИСТОЧНИК: // ТЗ: ТЗ 5.1 (сценарий, подход, чекбокс Гудвилл); 6.10 (статусная модель, маршрут по инициатору, № и дата решения УОБ, «новая версия КФО» после Верифицирован); 6.4 (причины отклонений от Методики) // Аналитика: md «Статусная модель»; md «Модель данных» v4 (consolidation_version: version_no, status varchar, initiator, approved_in_rf, verified_in_go, kfo_confirmation_enabled) // Макет: «Версия», «Дата верификации», «Включить/Отключить подтверждение КФО», «Смена статуса отчёта», «Ответственный / Утвердил / Верифицировал» // В модели: v4 + v5 (enum статусов, scenario, approach, goodwill_enabled, initiator_office_id, uob_*, methodology_deviations, previous_version_id) Table consolidation_version { id uuid [pk] consolidation_id uuid [not null, ref: > consolidation.id] version_no int [not null] status consolidation_status [not null, note: 'CHG: enum вместо varchar'] scenario consolidation_scenario [not null, note: 'ADD ТЗ 5.1'] approach consolidation_approach [not null, default: 'BASIC', note: 'ADD ТЗ 5.1'] goodwill_enabled boolean [not null, default: false, note: 'ADD ТЗ 5.1/6.7: чек-бокс расчёта Гудвилла'] initiator_id uuid [not null, ref: > app_user.id] initiator_office_id uuid [not null, ref: > regional_office.id, note: 'ГО/РФ инициатора — определяет маршрут (ТЗ 6.10)'] approved_in_rf_by uuid [ref: > app_user.id] approved_in_rf_at timestamptz verified_in_go_by uuid [ref: > app_user.id] verified_in_go_at timestamptz uob_decision_no varchar [note: 'ADD ТЗ 6.10: № решения УОБ'] uob_decision_date date [note: 'ADD ТЗ 6.10'] methodology_deviations text [note: 'ADD ТЗ 6.4: причины отклонений от Методики'] elimination_rules_version int [note: 'v5.11: версия elimination_rule (version_no), по которой считалась версия; фиксируется при первом расчёте, замораживается при VERIFIED'] is_in_work boolean [not null, default: true] previous_version_id uuid [ref: > consolidation_version.id, note: '«новая версия КФО» после VERIFIED'] cancelled_reason text created_at timestamptz created_by uuid [ref: > app_user.id] updated_by uuid [ref: > app_user.id] updated_at timestamptz Indexes { (consolidation_id, version_no) [unique] } Note: 'Версия консолидации. После VERIFIED — read-only; изменения только через новую версию' } // ИСТОЧНИК: // ТЗ: ТЗ 6.10 (переходы, возврат на доработку); 8.2 «изменение статусов; согласование и утверждение КФО» // Аналитика: md «Статусная модель» (переходы) // Макет: «Смена статуса отчёта», «Комментарий (обязательно)», «Отклонено: отчёт возвращён на доработку» // В модели: v5 новая Table consolidation_status_history { id uuid [pk] version_id uuid [not null, ref: > consolidation_version.id] from_status consolidation_status to_status consolidation_status [not null] changed_by uuid [not null, ref: > app_user.id] changed_at timestamptz [not null] comment text [note: 'обязателен при возврате на доработку'] Note: 'ADD ТЗ 6.10/8.2: маршрут согласования' } // ADD v5.1 (макет, ANL-036): «взять в работу» / перехват прав редактирования // ИСТОЧНИК: // ТЗ: нет в ТЗ (ANL-036) // Аналитика: нет // Макет: «Взятие КФО в работу (перехват прав редактирования)», «Причина: Отпуск», «В данный момент консолидация редактируется пользователем …» // В модели: v5.1 новая (макет) // Зачем: блокировка версии; подтвердить с заказчиком // v5.16: без истечения по времени (OQ-119): активна, пока не снята владельцем/администратором или не перехвачена по причине Table consolidation_lock { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] locked_by uuid [not null, ref: > app_user.id] locked_at timestamptz [not null] released_at timestamptz [note: 'NULL = активная блокировка'] taken_over_from uuid [ref: > app_user.id, note: 'при перехвате — предыдущий владелец'] takeover_reason text [note: 'обязателен при перехвате (макет: «Причина: Отпуск»)'] Indexes { consolidation_version_id [note: 'partial UNIQUE WHERE released_at IS NULL'] } Note: 'Ответственный за версию сейчас = активная блокировка' } // ИСТОЧНИК: // ТЗ: ТЗ 6.1 «КА в рамках консолидации редактирует состав ГСК»; 6.10 «выбор состава ГСК»; ЕХ 6.3 «Периметр на дату» // Аналитика: md «Сводная ОСВ» п.2 («зафиксированный периметр; изменение related_group_member не меняет версию»); md «Модель данных» v4 // Макет: «Состав ЮЛ», «Минимум для консолидации: 2», «Редактировать состав» // В модели: v4 + v5 (is_head_entity для Гудвилла) Table consolidation_member { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] legal_entity_id uuid [not null, ref: > legal_entity.id] is_head_entity boolean [not null, default: false, note: 'ADD: головная организация (для Гудвилла)'] Indexes { (consolidation_version_id, legal_entity_id) [unique] } Note: 'Зафиксированный периметр версии. Изменение related_group_member не меняет существующие версии' } // ИСТОЧНИК: // ТЗ: ТЗ Прил. 2 «Документ ФО: ссылка на Консолидацию и ЮЛ»; 6.4/6.5 «использование данных предыдущих периодов»; ЕХ 7 «повторное использование первички без дублей» // Аналитика: md «Сводная ОСВ» п.3 (файлы выбираются по ЮЛ+год+квартал — без явной связи) // Макет: «Первичные документы консолидации» // В модели: v5 новая; v5.13 — document_id вместо stored_file_id // Зачем: явная привязка документа к версии (переиспользование, монозаёмщики из ЕХ) // v5.16: точки привязки/отвязки — подтверждение документа, добавление ЮЛ в периметр; замена/удаление файла, исключение ЮЛ (Срез 4, D-5) Table consolidation_document { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] document_id uuid [not null, ref: > document.id, note: 'v5.13: логический документ (ОСВ по счёту / ББ / ОФР), не файл'] Indexes { (consolidation_version_id, document_id) [unique] } Note: 'ADD Прил. 2 ТЗ «Документ ФО → Консолидация»: явная привязка документов к версии (переиспользование в нескольких версиях/периодах, монозаёмщики из ЕХ). Чек-лист = required_document_rule × периметр − consolidation_document' } // ---- Корректировки ---- // ИСТОЧНИК: // ТЗ: ТЗ 6.8 (форма корректировок: колонки ДБ/КР/Комментарий, >5%, импорт/экспорт, отдельная форма ОФР); 6.4 (доп. корректировки ОФР); Прил. 2 «Расхождение/корректировка»; Прил. 9 // Аналитика: md «Корректировка данных» (correction: parent_correction_id, debit/credit_line_code, adjustment); md «Модель данных» v4 // Макет: «Таблица корректировки»: Актив/Пассив/Контроль/Корректировка, «Строка баланса Дт/Кт», «Комментарий» // В модели: v4 + v5 (statement_type, pair_match_id, reason_code, comment NOT NULL, status, version_no) Table correction { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] statement_type statement_type [not null, note: 'ADD: ББ или ОФР (ТЗ 6.8 — отдельные формы)'] rule_id uuid [ref: > elimination_rule.id, note: 'v5.10: пара ВГО по естественному ключу (правило + компании); NULL = ручная без пары'] parent_correction_id uuid [ref: > correction.id, note: 'дочерняя строка распределения по нескольким статьям; глубина 1'] legal_entity_id uuid [ref: > legal_entity.id, note: 'компания 1 (чья первичка корректируется)'] counterparty_id uuid [ref: > legal_entity.id, note: 'компания 2'] debit_line_code varchar debit_account_id uuid [ref: > chart_of_account.id] debit_adjustment numeric(18,2) credit_line_code varchar credit_account_id uuid [ref: > chart_of_account.id] credit_adjustment numeric(18,2) deviation_amount numeric(18,2) deviation_percent numeric(9,4) reason_code varchar [note: 'ADD ТЗ 6.4: unrealized_revenue | excise | agency_commission | intra_lease_depreciation | intra_loan_interest | equity_income | other'] comment text [not null, note: 'ТЗ 6.8: комментарий обязателен'] status correction_status [not null, default: 'DRAFT'] version_no int [not null, default: 1, note: 'сноска 22 ТЗ: история версий корректировки — audit_event(CORRECTION_EDIT, before/after); инкремент при каждом изменении'] created_by uuid [ref: > app_user.id] created_at timestamptz updated_by uuid [ref: > app_user.id] updated_at timestamptz Indexes { consolidation_version_id (consolidation_version_id, rule_id, legal_entity_id, counterparty_id) } Note: 'Overlay поверх первички: итог = свод первички − Σ применённых корректировок. Расхождение пары (deviation_*) — справочно, пересчитывается' } // ---- Гудвилл (ТЗ 6.7, Прил. 3 п.2, Прил. 8) ---- // ИСТОЧНИК: // ТЗ: ТЗ 6.7 (Гудвилл: ФВ ГО − доля в капитале ДО, комментарий обязателен, отдельная строка ББ); Прил. 3 п.2 (58.01 против 80/83, стр. 1310/1350/1370); Прил. 8 // Аналитика: md «Гудвилл» (шаги 1–10: 58.01, стр. 1310, доля владения, include_in_kfo); md «Модель данных» v4 (goodwill) // Макет: «Таблица гудвилла», «Доля владения», «Учет в отчете», «Изменение долей владения (гудвилл)» // В модели: v4 + v5 (additional_capital_amount, comment) Table goodwill { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] head_legal_entity_id uuid [not null, ref: > legal_entity.id] subsidiary_legal_entity_id uuid [not null, ref: > legal_entity.id] ownership_share numeric(7,4) [note: 'ручной ввод, %'] financial_investment_amount numeric(18,2) [note: 'ΣДБ 58.01 ГО по ДО'] charter_capital_amount numeric(18,2) [note: 'стр. 1310 ББ ДО на отчётную дату'] additional_capital_amount numeric(18,2) [note: 'ADD ТЗ Прил. 3: стр. 1350 ББ ДО'] participation_amount numeric(18,2) [note: '(1310 + 1350) * share / 100 — DECISION: уточнить у методологов, входит ли 1350 в базу'] goodwill_amount numeric(18,2) include_in_kfo boolean [not null, default: false] comment text [note: 'ТЗ 6.7: обязателен при include_in_kfo'] created_by uuid [ref: > app_user.id] created_at timestamptz updated_by uuid [ref: > app_user.id] updated_at timestamptz Indexes { (consolidation_version_id, head_legal_entity_id, subsidiary_legal_entity_id) [unique] } } // ---- Результат КФО (ТЗ 6.4, Прил. 10) ---- // ИСТОЧНИК: // ТЗ: ТЗ 6.4 (итоговая форма КФО: подход, Гудвилл, расхождения, корректировки, верификация); 6.8 (контрольные пороги 10% ЧА / 1% ВБ, 10% прибыли / 1% выручки); Прил. 10 // Аналитика: нет (пробел — ANL-008/009) // Макет: «КФО», «Сводка расхождений КФО», «Есть несохранённые изменения в КФО» // В модели: v5 новая // Зачем: шапка результата + пороги Table kfo_result { id uuid [pk] consolidation_version_id uuid [not null, unique, ref: > consolidation_version.id] computed_at timestamptz [not null] is_frozen boolean [not null, default: false, note: 'true после VERIFIED'] net_assets numeric(18,2) [note: 'ЧА ГСК'] balance_total numeric(18,2) [note: 'ВБ ГСК'] revenue numeric(18,2) sales_profit numeric(18,2) total_deviation_bb numeric(18,2) [note: 'Σ расхождений по статьям ББ'] total_deviation_ofr numeric(18,2) threshold_bb_exceeded boolean [note: 'ТЗ 6.8: > 10% ЧА (или > 1% ВБ, если ЧА < 10% ВБ)'] threshold_ofr_exceeded boolean [note: '> 10% прибыли от продаж (или > 1% выручки при рентабельности < 10%)'] threshold_justification text [note: 'обоснование принятия риска при превышении'] goodwill_total numeric(18,2) has_deviations boolean Note: 'Шапка результата КФО + контрольные пороги' } // ИСТОЧНИК: // ТЗ: ТЗ 6.4 «шаг 1 — сложение по кодам строк; шаг 2 — элиминация; неэлиминируемая часть остаётся»; Прил. 2 «Результат КФО: статья, значение»; Прил. 10; ФСБУ 4/2023 // Аналитика: нет (пробел — ANL-008) // Макет: колонки «в т.ч. взаимные обязательства», «Очищенные показатели», «Расхождения», «источник: Баланс/ОСВ», «в т.ч. Гудвилл» // В модели: v5 новая + v5.1 (vgo_amount, source_kind, deviation_amount, comment) Table kfo_result_line { id uuid [pk] kfo_result_id uuid [not null, ref: > kfo_result.id] statement_type statement_type [not null] line_code varchar [not null, note: 'код строки ФСБУ 4/2023; GOODWILL — отдельная строка раздела I'] line_name varchar sum_amount numeric(18,2) [note: 'шаг 1: простое сложение по компаниям'] eliminated_auto numeric(18,2) [note: 'шаг 2: авто-элиминация по правилам'] eliminated_manual numeric(18,2) [note: 'Σ применённых корректировок'] goodwill_adjust numeric(18,2) consolidated_amount numeric(18,2) [not null, note: 'итог («Очищенные показатели» в макете)'] vgo_amount numeric(18,2) [note: 'v5.1 макет: «в т.ч. взаимные обязательства/обороты» = eliminated_auto + eliminated_manual'] source_kind varchar [note: 'v5.1 макет: balance | osv — источник значения статьи'] deviation_amount numeric(18,2) [note: 'v5.1 макет «Расхождения» по статье'] comment text [note: 'v5.1 макет: пояснение к строке КФО'] ordinal int Indexes { (kfo_result_id, statement_type, line_code) [unique] } } // ИСТОЧНИК: // ТЗ: ЕХ ТЗ 6.6 «выгрузка расшифровки статей отчётности в разрезе контрагентов»; ТЗ 6.9 (настраиваемые поля экспорта) // Аналитика: нет // Макет: «Доля в показателе по группе, %», расшифровка по компаниям // В модели: v5 новая + v5.1 (share_percent) Table kfo_result_line_company { kfo_result_line_id uuid [not null, ref: > kfo_result_line.id] legal_entity_id uuid [not null, ref: > legal_entity.id] amount numeric(18,2) share_percent numeric(7,4) [note: 'v5.1 макет: «Доля в показателе по группе, %»'] Indexes { (kfo_result_line_id, legal_entity_id) [pk] } Note: 'Разрез по компаниям для расшифровки статьи (ЕХ 6.6 «расшифровка в разрезе контрагентов»)' } // ---- Сценарий «Подтверждение КФО клиента» (ТЗ 5.1, 6.10) ---- // ИСТОЧНИК: // ТЗ: ТЗ 5.1 «подтверждение консолидированной отчётности, предоставленной Клиентом»; 6.10 «ручной ввод данных КФО клиента; сравнение и анализ расхождений»; Методика сноска 14 (процент к наименьшему) // Аналитика: md «Статусная модель» (kfo_confirmation_enabled — только флаг; ANL-006) // Макет: «Подтверждение КФО клиента»: Версия клиента / Версия банка, ВГО клиента / ВГО Банка, Расхождение, Причина // В модели: v5 новая + v5.1 (client_vgo/bank_vgo/deviation_reason) Table client_kfo_line { id uuid [pk] consolidation_version_id uuid [not null, ref: > consolidation_version.id] statement_type statement_type [not null] line_code varchar [not null] client_amount numeric(18,2) [note: 'ручной ввод из КФО клиента («Консолидированная отчетность от клиента»)'] client_vgo_amount numeric(18,2) [note: 'v5.1 макет: «ВГО клиента» — ручной ввод'] bank_amount numeric(18,2) [note: 'из kfo_result_line на момент сравнения'] bank_vgo_amount numeric(18,2) [note: 'v5.1 макет: «ВГО Банка» из kfo_result_line.vgo_amount'] deviation_amount numeric(18,2) deviation_percent numeric(9,4) [note: 'относительно наименьшего (Методика сноска 14)'] deviation_reason text [note: 'v5.1 макет: «Причина расхождения»'] comment text entered_by uuid [ref: > app_user.id] entered_at timestamptz Indexes { (consolidation_version_id, statement_type, line_code) [unique] } } // ============ КОММЕНТАРИИ, АУДИТ, ЭКСПОРТ ============ // ИСТОЧНИК: // ТЗ: ТЗ 6.3 «возможность добавления комментария к отчётности»; 6.4 «комментарии» в форме КФО; 6.5/6.8 «обязательный комментарий» // Аналитика: md «Загрузка документов» («Комментарий к документу») // Макет: «Новый комментарий», «Комментарий к документу», комментарии в ячейках шахматки, «Редактировать комментарии» в КФО // В модели: v5 новая // Зачем: полиморфные комментарии к объектам Table comment { id uuid [pk] object_type varchar [not null, note: 'document | consolidation_version | chess_cell(ключ пары) | kfo_result_line | …'] object_id uuid [not null] text text [not null] author_id uuid [not null, ref: > app_user.id] created_at timestamptz [not null] Indexes { (object_type, object_id) } Note: 'ADD ТЗ 6.3/6.4: комментарии к отчётности и КФО (в т.ч. обязательные при расхождениях)' } // ИСТОЧНИК: // ТЗ: ТЗ 8.2 (журнал изменений, логирование всех действий, перечень событий: вход/выход, создание консолидации, ГСК/ГК, загрузка, распознанные данные, корректировки, статусы, утверждение, выгрузка; хранение ≥ срока данных); Прил. 2 «Запись журнала изменений» // Аналитика: нет (пробел — ANL-019); md «Ролевая модель» ссылается на «журнал аудита» // Макет: «Журнал действий»: разделы, Объект/Событие/Что изменилось/Результат/Идентификатор, IP/браузер, «неверный пароль» // В модели: v5 новая + v5.1 (section, result, changed_field, user_agent) Table audit_event { id uuid [pk] occurred_at timestamptz [not null] user_id uuid [ref: > app_user.id, note: 'NULL для системных событий'] action audit_action [not null] section audit_section [not null, note: 'v5.1 макет: раздел журнала'] object_type varchar [not null] object_id uuid consolidation_version_id uuid [ref: > consolidation_version.id, note: 'для выборки «история изменений КФО»'] description text before_value jsonb after_value jsonb comment text result varchar [note: 'v5.1 макет: Успешно / Отклонено / Ошибка'] changed_field varchar [note: 'v5.1 макет: фильтр «Изменяемое поле»'] ip_address varchar user_agent varchar [note: 'v5.1 макет: браузер в событиях сеанса'] Indexes { (consolidation_version_id, occurred_at) (object_type, object_id) (user_id, occurred_at) } Note: 'ТЗ 8.2: журнал изменений/аудит. Append-only. Хранение ≥ срока хранения консолидации. Также покрывает: историю правок распознанных данных, факт/состав выгрузок (ЕХ 6.6), импорт справочников, изменения матрицы прав' } // ============ ЕХФД (ТЗ BIQ-34649) ============ // DECISION-2: срез = замороженный указатель на верифицированную версию + список её артефактов. // ИСТОЧНИК: // ТЗ: ЕХ ТЗ 6.2 «запись срезами: одна ГСК на одну отчётную дату, после Верифицирован»; 6.4 «версии среза Актуальная/Архив, неизменяемость»; 5.3 «архивирование в data lake» // Аналитика: нет (пробел — ANL-032) // Макет: «Единое хранилище», «Сформированная отчетность», «Верифицирована» // В модели: v5 новая (DECISION-2) // Зачем: замороженный указатель на верифицированную версию Table ex_snapshot { id uuid [pk] related_group_id uuid [not null, ref: > related_group.id] company_group_id uuid [not null, ref: > company_group.id, note: 'денормализация на момент верификации'] reporting_date date [not null] year smallint [not null] quarter period_quarter [not null] version_no int [not null] status ex_snapshot_status [not null, note: 'ACTUAL — последняя верифицированная; предыдущие → ARCHIVE'] source_version_id uuid [not null, unique, note: 'v5.12: id consolidation_version-источника БЕЗ FK — источник удаляется по сроку, срез нет'] source_version_no int [not null] superseded_by_snapshot_id uuid [ref: > ex_snapshot.id, note: 'v5.12: для ARCHIVE — ссылка на актуальный срез, который его заместил'] verified_at timestamptz [not null] written_at timestamptz [not null] written_by uuid [ref: > app_user.id, note: 'инициатор записи'] archived_at timestamptz data_lake_ref varchar [note: 'ссылка на архив в data lake'] Indexes { (related_group_id, year, quarter, version_no) [unique] (related_group_id, year, quarter, status) } Note: 'Срез данных ЕХ: одна ГСК на одну отчётную дату (ЕХ 6.2). Неизменяем и самодостаточен (не зависит от таблиц консолидации). Состав — ex_snapshot_item + ex_snapshot_member' } // ИСТОЧНИК: // ТЗ: ЕХ ТЗ 6.3 «Периметр на дату: состав ЮЛ/ИП группы на отчётную дату» // Аналитика: нет // Макет: дерево «ГК → ГСК → Организации» с ИНН // В модели: v5 новая // Зачем: денормализация периметра на дату среза Table ex_snapshot_member { ex_snapshot_id uuid [not null, ref: > ex_snapshot.id] legal_entity_id uuid [not null, ref: > legal_entity.id] inn varchar [not null, note: 'денормализация на дату среза'] name varchar [not null] Indexes { (ex_snapshot_id, legal_entity_id) [pk] } Note: 'Периметр на дату (ЕХ 6.3)' } // ИСТОЧНИК: // ТЗ: ЕХ ТЗ 5.2 «Состав хранимых данных» (КФО, первичные, сводная ОСВ, шахматка, гудвилл, корректировки, иные отчёты); 6.7.3/6.7.4 «Сформированная отчётность / Первичные документы», «Просмотр / Выгрузка» // Аналитика: нет // Макет: область документов ЕХ, «Скачать», «Связать» документ с организацией // В модели: v5 новая; v5.13 — document_id вместо stored_file_id // Зачем: состав среза со ссылками на объекты Table ex_snapshot_item { id uuid [pk] ex_snapshot_id uuid [not null, ref: > ex_snapshot.id] item_type varchar [not null, note: 'primary_document | summary_osv | vgo_pairs | chess | kfo_balance | kfo_income | goodwill | corrections | other_report'] legal_entity_id uuid [ref: > legal_entity.id, note: 'для первички — организация'] document_id uuid [ref: > document.id, note: 'v5.13: только для primary_document — исходники хранятся весь срок, FK допустим; «Скачать» — через document.stored_file → s3_object'] payload jsonb [note: 'v5.12 DECISION-5: замороженные данные для всех остальных типов; самодостаточно, без ссылок на таблицы консолидации'] payload_schema_version varchar Indexes { (ex_snapshot_id, item_type) } Note: 'Состав среза: primary_document → document; всё остальное → payload. «Просмотр» в ЛК ЕХ рендерит payload по схеме; «Выгрузка» собирает xlsx из payload на лету' } // ============ ПРОЧЕЕ ============ // ИСТОЧНИК: // ТЗ: ТЗ 5.1 (базовый / расширенный пакет ОСВ); 6.2 «набор обязательных ОСВ»; сноска 12 «процесс останавливается, если отсутствует документ» // Аналитика: md «Загрузка документов» (чек-лист: «ОСВ по обязательным счетам», «прочие документы») // Макет: «Чек-лист загруженных документов», «Условные обозначения статусов» // В модели: v5 новая // Зачем: конфигурация чек-листа по подходу; с v5.13 сверяется с document (тип + счёт + период), а не с файлом Table required_document_rule { id uuid [pk] approach consolidation_approach [not null] document_type document_type [not null] account_id uuid [ref: > chart_of_account.id, note: 'для OSV'] is_mandatory boolean [not null, note: 'обязателен → блокирует переход к консолидации (сноска 12 ТЗ)'] Indexes { (approach, document_type, account_id) [unique] } Note: 'ADD ТЗ 5.1/6.2: состав обязательных документов по подходу — основа чек-листа. Выполнено = есть document CONFIRMED с таким типом/счётом за период у каждого ЮЛ периметра' }