Entity: CurrencyEntity
Дом полей справочной записи «Валюта» контекста handbook. Проекции (lookup-выдачи, CurrencySnapshot,
событие pin.handbook.currencies.v1, CurrencyId в денежных контрактах chessboard/crm/rewards/
investor-owner/promotion) ссылаются сюда и описывают только дельту.
Назначение
CurrencyEntity — реплика записи валюты из Talent Handbook
(ADR-0063). Даёт всем денежным
контрактам платформы типизированную ссылку CurrencyId short и display-атрибуты (код ISO-4217, название,
знак) вместо ad-hoc строковых кодов. Записи создаёт и обновляет только синк источника; администратору
доступны archive/restore.
Наличие справочника валют не пересматривает
ADR-0048: вся денежная
арифметика PIN остаётся RUB-only. CurrencyId в контрактах — типизированная ссылка на запись
справочника и источник display-атрибутов (через CurrencySnapshot), а не обещание конверсий,
кросс-валютных расчётов или хранения балансов в разных валютах. Допустимость валюты в конкретной денежной
операции по-прежнему ограничивается правилами ADR-0048; справочник лишь отвечает на вопрос «что это за
валюта и как её показать».
IsoCode (ISO-4217) — единственное легитимное char(3) в спеке PIN: вне справочника контракты
сервисов строковых кодов валют не несут — только CurrencyId + CurrencySnapshot (display-код живёт
внутри снапшота, см. snapshots.md).
Классификация
| Поле | Значение |
|---|---|
| Контекст | handbook |
| Kind | Replica (source of truth — Talent Handbook) |
| Source of truth | talent.microservices.handbook, сущность Currency; признак архива IsArchived — handbook |
| Таблица | handbook.currencies |
| Tenant isolation | не tenanted — платформенный справочник |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | short | Да | PK | синк (= id Talent) | Идентификатор валюты = id записи источника (ADR-0063 п.2). |
Name | string | Да | varchar(64), min 1 | синк | Название валюты («Российский рубль»). |
IsoCode | string | Да | char(3), upper; unique среди неархивных ux_currencies_iso_code | синк | Код ISO-4217 (RUB). В источнике поле — varchar(16) (длина не гарантирована); реплика нормализует (trim + upper) и хранит ровно char(3). INV-HND-2, INV-HND-6. |
Sign | string? | Нет | varchar(16) | синк | Знак/символ валюты («₽»). |
IsArchived | bool | Да | default false | admin / reconciliation | Soft-архив (delete-gap источника, ADR-0063 п.5). |
LastEventAt | DateTimeOffset | Да | — | синк (envelope CreatedAt currency-события) | Время последнего применённого события источника. Заменяет ReplicaVersion: у currency-событий источника нет per-record Version, монотонность обеспечивается по времени события (envelope CreatedAt) — INV-HND-4 в time-based-форме, как у региона (region.md; ADR-0063 п.1). |
CreatedAt / UpdatedAt | DateTimeOffset | Да | — | синк / admin | Штампы создания/изменения. |
Нормализация IsoCode: источник хранит код в varchar(16), реплика применяет trim + upper и принимает
только валидный ISO-4217 — ровно 3 буквы. Значение длиной ≠ 3 после trim синк не применяет:
currency-событие уходит в reject/DLQ с warn-метрикой (данные правятся в источнике) — INV-HND-6,
ADR-0063 п.2. В реплику (и, как следствие, в CurrencySnapshot) попадают только валидные char(3)-коды.
Связи
| Связь | Тип | Ключ | Delete behavior |
|---|---|---|---|
CountryEntity | логическая обратная (без FK) | Country.DefaultCurrencyId → Currency.Id | архив валюты ссылку не ломает (резолв включает архивные, INV-HND-5) |
Денежные контракты потребителей (chessboard, crm, rewards, investor-owner, promotion, messenger) | логическая (чужие контексты, без FK) | CurrencyId + CurrencySnapshot в событиях; resolve на admin-write | исторические ссылки переживают архив |
Инварианты
INV-HND-1 (создаёт/меняет только синк), INV-HND-2 (уникальность ISO среди неархивных), INV-HND-4
(монотонность применения — в time-based-форме по LastEventAt, см. ниже), INV-HND-5 (lookup — только
неархивные; resolve — включая архивные) — объявлены в country.md и действуют для валюты.
INV-HND-6 (well-formedness IsoCode) специфичен для валюты (источник хранит код в varchar(16)) и
объявлен здесь. Применение к валюте (единственный код — IsoCode):
| Инвариант | Условие | Когда проверяется | Ошибка |
|---|---|---|---|
| INV-HND-2 UniqueIsoAmongActive (применение) | IsoCode уникален среди неархивных валют | при применении события синка; при admin restore | синк: HANDBOOK_SYNC_ISO_CONFLICT → DLQ (non-retriable); restore: тот же код, HTTP 409 |
| INV-HND-4 MonotonicVersion (time-based-форма) | currency-событие применяется только при envelope CreatedAt строго больше LastEventAt; per-record Version у источника нет (ADR-0063 п.1) | при применении события синка | — (skip + метрика stale) |
| INV-HND-6 IsoCodeWellFormed | IsoCode источника (varchar(16)) после trim — ровно 3 буквы (валидный ISO-4217); нормализуется в upper и хранится char(3) | при применении события синка | длина ≠ 3 → HANDBOOK_SYNC_INVALID_ISO_CODE → DLQ (non-retriable) + warn-метрика |
Жизненный цикл и операции
| Операция | Актор | Предусловие | Эффект | Отказ |
|---|---|---|---|---|
| Upsert (синк) | consumer группы pin-handbook-talent-sync | envelope CreatedAt > LastEventAt (INV-HND-4); IsoCode валиден — 3 буквы после trim (INV-HND-6) и не конфликтует (INV-HND-2) | создание/перезапись всех реплика-полей (snapshot-семантика) + re-publish currency-upserted | stale → skip; невалидный IsoCode → DLQ (HANDBOOK_SYNC_INVALID_ISO_CODE); ISO-конфликт → DLQ pin.handbook.talent-sync-dlq.v1 (HANDBOOK_SYNC_ISO_CONFLICT) |
| Auto-restore (синк) | consumer | запись заархивирована, из источника пришёл её upsert | IsArchived = false + warn-метрика + re-publish | — |
| Archive / Restore | администратор платформы | запись существует; для restore — IsoCode свободен среди неархивных | IsArchived переключён; re-publish; повторный вызов — идемпотентный no-op | 404 HANDBOOK_ENTRY_NOT_FOUND; restore: 409 HANDBOOK_SYNC_ISO_CONFLICT |
| Lookup (list) | любой (включая гостя) | — | неархивные валюты (INV-HND-5) | — |
| Resolve (batch by id) | внутренние сервисы (m2m: валидация CurrencyId на admin-write денежных контрактов) | — | записи по id, включая архивные (INV-HND-5) | неизвестный id отсутствует в ответе |
События
| Событие | Топик | Когда публикуется | Payload |
|---|---|---|---|
currency-upserted | pin.handbook.currencies.v1 (compacted, key = Id, ContractVersion 1) | применённый upsert синка; admin archive/restore; пересинхронизация | полный snapshot реплики, включая IsArchived и LastEventAt — ../events/currency-upserted.md |
chessboard (локальная реплика: валидация CurrencyId на write-path проектов/цен, display-атрибуты).
Ошибки
| Код | HTTP | Условие |
|---|---|---|
HANDBOOK_ENTRY_NOT_FOUND | 404 | точечное чтение / archive / restore несуществующей валюты |
HANDBOOK_SYNC_ISO_CONFLICT | 409 (admin restore) / DLQ, non-retriable (синк) | нарушение INV-HND-2: IsoCode занят другой неархивной валютой |
HANDBOOK_SYNC_INVALID_ISO_CODE | DLQ, non-retriable (синк) | нарушение INV-HND-6: IsoCode источника (varchar(16)) после trim не равен 3 буквам; в реплику попадают только валидные ISO-4217 (char(3)) |
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_currencies; ux_currencies_iso_code unique (upper(iso_code)) where not is_archived |
| Партиционирование / retention | none — десятки строк; retention бессрочный |
| Concurrency | события применяются последовательно per-key; admin-операции — оптимистическая блокировка |
| Permissions / PII | archive/restore — admin API (handbook.entries.manage); lookup — публичный; resolve — internal m2m. PII нет |
| Audit | LastEventAt + штампы; admin archive/restore логируется с актором |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (8): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity / Aggregate: CommissionAccrualEntity, Entity / Aggregate: InvestorPropertyEntity, Entity / Aggregate: PromotionPackage, Entity / Aggregate: WalletAccountEntity, Entity: CountryEntity, Snapshot-модели справочника (единый дом платформы)
- API (8): GET /api/v1/handbook/{countries,regions,cities,currencies,languages} — Публичный lookup справочников, GET /api/v1/portfolio/summary — Сводка доходности портфеля (getMyPortfolioSummary), Handbook API — реестр операций областей public / internal / admin, Investor-Owner API — реестр операций и общие контрактные модели, POST /api/v1/admin/handbook/{type}/{id}/{archive,restore} — Архив и восстановление справочной записи, POST /api/v1/admin/portfolio/properties — Создание записи портфеля вручную (admin, компенсирующее), POST /internal/handbook/resolve — Batch-резолв справочных записей по id, PUT /api/agent/crm/leads/{leadId}/wishes — Обновить хотелки лида
- Use Cases (2): UC-HND-002. Система синхронизирует реплику справочника из Talent, US-реестр контекста handbook (US-HND)
- События (1): Event Contract: currency-upserted (CurrencyUpsertedEvent)
- Консюмеры (2): Consumer: синхронизация из Talent Owners (pin-investor-owner-talent-owners-sync), Consumer: Talent Handbook Sync (pin-handbook-talent-sync)
- Фоновые задачи (1): Background Job: talent-initial-import (bootstrap и reconciliation справочников из Talent Handbook)
- index.md (1): Handbook Service
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- country.md — INV-HND-1/4/5 и
DefaultCurrencyId; snapshots.md —CurrencySnapshotи правило «деньги =CurrencyId+CurrencySnapshot». - talent-replica.md — правила синка; ../consumers/talent-handbook-sync.md — consumer; ../events/currency-upserted.md — контракт события.
- ADR-0048 — RUB-only, не пересматривается; ADR-0063 п.7.