# Bitemporal модель Каждая запись Ordinis имеет двухмерное время: - **Бизнес-время** (`validFrom` / `validTo`) — когда запись действует в реальности. - **Транзакционное время** (Hibernate Envers `revinfo.timestamp`) — когда изменение зафиксировано в БД. Это две независимые оси: можно ввести запись задним числом (business → past, txTime → now), либо запланировать на будущее (business → future, txTime → now), либо retroactively изменить прошлое (исправить ошибку — txTime → now, но business range уже past). ## Поля ```json { "businessKey": "RESURS-P-3", "validFrom": "2025-01-01T00:00:00+03:00", "validTo": "9999-12-31T23:59:59+03:00", "version": 2, "data": { ... } } ``` | Поле | Тип | Описание | |---|---|---| | `validFrom` | timestamp tz | Когда версия начинает действовать. | | `validTo` | timestamp tz | До какого момента (или `9999-12-31` если бессрочно). | | `version` | int | Optimistic lock + порядковый номер версии. Monotonic per businessKey. | ## Update создаёт **новую версию** Update — это не overwrite. Это: 1. Старая запись: `validTo = now()` (закрыта). 2. Новая запись: `validFrom = now()`, `validTo = 9999...`, `version + 1`. Обе версии остаются в БД. История доступна через `GET /records/{key}/history` (см. [Read endpoints](read-endpoints.md)). ```mermaid gantt dateFormat YYYY-MM-DD title Версии записи RESURS-P-3 section v1 v1 (status=in-orbit) :2024-06-01, 2024-12-30 section v2 v2 (status=active) :2024-12-30, 2026-05-07 section v3 v3 (name updated) :2026-05-07, 9999-12-31 ``` ## Запросы на момент `?at=2025-06-15` возвращает версию, которая была активна 15 июня 2025: ```bash curl "https://ordinis.k8s.264.nstart.cloud/api/v1/spacecraft/records/RESURS-P-3?at=2025-06-15T00:00:00Z" ``` → v2 (status=active), потому что `2024-12-30 ≤ 2025-06-15 < 2026-05-07`. Без `?at` — default = `now()`. ## Future-dated entries Можно ввести запись в будущее: ```json PUT /api/v1/dictionaries/spacecraft/records/MISSION-2027 { "validFrom": "2027-01-01T00:00:00+03:00", "data": { "name": "Запланированный КА", "status": "planned" } } ``` Запись существует в БД, но `GET ?at=now` её не вернёт (потому что `now < validFrom`). Появится автоматически 1 января 2027. ## Закрытие записи (close) ```bash POST /api/v1/dictionaries/spacecraft/records/RESURS-P-3/close { "at": "2026-05-07T12:00:00+03:00", "reason": "Списан с орбиты" } ``` Устанавливает `validTo = at`. После этого: - `GET ?at=2026-05-07T11:00:00Z` → возвращает запись (ещё активна). - `GET ?at=2026-05-07T13:00:00Z` → 404 (уже закрыта). - `GET ?at=2026-05-07T11:30:00Z&include_closed=true` → для UI history. Outbox emit'ит event `action: closed`. ## Bulk close Закрытие нескольких записей одной транзакцией: ```bash POST /api/v1/dictionaries/spacecraft/records/_bulk-close { "businessKeys": ["RESURS-P-1", "RESURS-P-2", "RESURS-P-3"], "at": "2026-05-07T00:00:00+03:00", "reason": "Программа Ресурс-П завершена" } ``` Response: ```json { "closed": 3, "skipped": [], "errors": [] } ``` Каждая запись в собственной sub-transaction (Spring AOP per-record). Если одна fail'нула с invalid_key — она в `errors`, остальные продолжают. ## Bulk export CSV ```bash GET /api/v1/dictionaries/spacecraft/records/_export?format=csv&lang=ru-RU ``` Stream CSV (RFC 4180) с активными на now записями. Используется UI bulk operations. Параметры — те же что list endpoint (`lang`, `at`, фильтры). ## Использование для consumer'ов ### Snapshot at order time Когда нужен «как было на момент заказа» — два варианта: **Вариант A: query с `?at=`** ```bash GET /api/v1/spacecraft/records/RESURS-P-3?at=2025-06-15T10:00:00Z ``` Pro: всегда свежие данные если возможна retroactive correction. Con: depends on Ordinis availability + retention. **Вариант B: snapshot в свою БД** ```sql CREATE TABLE imaging_order ( id UUID PRIMARY KEY, spacecraft_business_key VARCHAR(256), reference_snapshot JSONB, -- копии записей, не FK created_at TIMESTAMPTZ DEFAULT NOW() ); ``` Pro: read-only к Ordinis, не зависит от availability/retention. Con: stale если Ordinis retroactively исправил данные. Альтум использует **Вариант B** (snapshot) для legal compliance — заказы attestation требуют не depending on external service. ## Update с конкретным validFrom Не всегда нужно `validFrom = now()`. Можно указать: ```bash PUT /api/v1/dictionaries/spacecraft/records/RESURS-P-3 { "validFrom": "2026-05-15T00:00:00+03:00", "data": { "name": "Ресурс-П № 3 (новое имя с 15 мая)" } } ``` Старая версия закрывается с `validTo = 2026-05-15`. Новая `validFrom = 2026-05-15`. До 15 мая `?at=now` возвращает старую, после — новую. ## Дальше - [Cross-references (FK)](x-references.md) — связи между записями - [Caching](caching.md) — учитываем bitemporal в инвалидации