Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
69 lines
6.0 KiB
Markdown
69 lines
6.0 KiB
Markdown
# data-model Specification
|
||
|
||
## Purpose
|
||
Схема базы данных ядра совпадает с моделью данных DBML v5.13 один в один: таблицы, колонки, типы, enum'ы, ключи и
|
||
индексы; миграции применяются к пустой базе и полностью откатываются; расхождение между ORM и DBML невозможно пропустить.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Схема БД равна DBML v5.14
|
||
ORM-метаданные SHALL содержать ровно 43 таблицы и 27 enum'ов из `../docs/model/data-model-v5.14.dbml` в схеме `dkb`;
|
||
имена таблиц, колонок, enum'ов и их значений SHALL совпадать с DBML буквально; nullability, PK, UNIQUE, FK, значения
|
||
по умолчанию и индексы SHALL соответствовать DBML, включая частичные уникальные индексы (`related_group_member`
|
||
`(related_group_id, legal_entity_id)` и `(legal_entity_id, kind)` WHERE `is_current`, `consolidation_lock` WHERE
|
||
`released_at IS NULL`) и `NULLS NOT DISTINCT` на `document (stored_file_id, document_type, osv_account_code)`.
|
||
`related_group_member.kind` SHALL быть NOT NULL и равняться `related_group.kind` группы (проверяет ядро при вставке).
|
||
`audit_action` SHALL содержать `LEGAL_ENTITY_CHANGE` и `USER_CHANGE`. Поля, отсутствующие в DBML, MUST NOT существовать.
|
||
|
||
#### Scenario: Сверка ORM с DBML
|
||
- **WHEN** выполняется `python scripts/check_dbml_vs_orm.py ../../docs/model/data-model-v5.14.dbml`
|
||
- **THEN** вывод пуст и код возврата 0
|
||
|
||
#### Scenario: Лишняя колонка в ORM
|
||
- **WHEN** в ORM добавлена колонка, которой нет в DBML (или удалена существующая)
|
||
- **THEN** скрипт печатает `таблица.колонка: отсутствует в DBML` (или `отсутствует в ORM`) и возвращает код 1
|
||
|
||
#### Scenario: Расхождение типа или nullability
|
||
- **WHEN** колонка `osv_entry.opening_debit` объявлена в ORM как `Numeric(18, 4)` или `NOT NULL`
|
||
- **THEN** скрипт сообщает `ожидалось numeric(18,2) NULL / фактически numeric(18,4) NOT NULL` и возвращает код 1
|
||
|
||
#### Scenario: ЮЛ одновременно в ГСК и ГСЗ
|
||
- **WHEN** для одного `legal_entity_id` вставлены две текущие строки `related_group_member` с `kind = gsk` и `kind = gsz`
|
||
- **THEN** обе вставки проходят; третья текущая строка с `kind = gsk` нарушает уникальный индекс
|
||
|
||
### Requirement: Миграции применяются с нуля и откатываются
|
||
Цепочка ревизий `0001_dbml_v5_13` → `0002_dbml_v5_14` SHALL создавать схему `dkb` (если нет), все enum'ы, таблицы,
|
||
ограничения, индексы и последовательности кодов (`company_group_code_seq`, `related_group_code_seq`) на пустой базе
|
||
PostgreSQL 16 (требуется ≥ 15); `downgrade` до `base` SHALL удалять всё созданное (включая новые значения `audit_action`
|
||
через пересоздание типа), оставляя пустую схему; повторный `upgrade` после `downgrade` SHALL проходить без ошибок.
|
||
|
||
#### Scenario: Полный цикл
|
||
- **WHEN** на пустой БД выполняются `alembic upgrade head`, `alembic downgrade base`, `alembic upgrade head`
|
||
- **THEN** все три команды завершаются успешно, после последней в `information_schema.tables` схемы `dkb` 43 таблицы модели (+ служебная `alembic_version`)
|
||
и в `pg_type` 27 enum-типов
|
||
|
||
#### Scenario: Откат на один шаг
|
||
- **WHEN** после `upgrade head` выполняется `alembic downgrade 0001_dbml_v5_13`
|
||
- **THEN** колонка `related_group_member.kind` отсутствует, индекс `(legal_entity_id) WHERE is_current` восстановлен,
|
||
`audit_action` не содержит `LEGAL_ENTITY_CHANGE`/`USER_CHANGE`
|
||
|
||
#### Scenario: Автогенерация ничего не находит
|
||
- **WHEN** после `upgrade head` выполняется `alembic revision --autogenerate` на той же БД
|
||
- **THEN** сгенерированная ревизия не содержит операций (пустые `upgrade`/`downgrade`)
|
||
|
||
### Requirement: Версия DBML зафиксирована в коде
|
||
Пакет моделей SHALL объявлять константу версии DBML (`5.14`); скрипт сверки SHALL сравнивать её с версией в шапке файла
|
||
DBML и падать при расхождении — смена модели без осознанного обновления ORM невозможна.
|
||
|
||
#### Scenario: Новая версия DBML без обновления ORM
|
||
- **WHEN** в `docs/model/` появляется `data-model-v5.15.dbml`, а константа в ORM — `5.14`
|
||
- **THEN** сверка против нового файла падает с сообщением о версии до сравнения таблиц
|
||
|
||
### Requirement: Деньги и проценты — точные типы
|
||
Все денежные колонки SHALL быть `numeric(18,2)`, проценты — `numeric(7,4)`/`numeric(9,4)`, уверенность — `numeric(5,2)`;
|
||
ORM SHALL отдавать их как `Decimal`, `float` в моделях MUST NOT встречаться.
|
||
|
||
#### Scenario: Чтение денежной колонки
|
||
- **WHEN** из `osv_entry` читается строка со `closing_debit = 106311116.46`
|
||
- **THEN** значение имеет тип `Decimal` и равно `Decimal("106311116.46")`
|