feat(fansy-store): DDL принимающей БД + контракт данных для команды Fansy
- docs/fansy-contract/v1/ddl/000__roles.sql: роли fansy_etl, bj_reader, bj_migrator - docs/fansy-contract/v1/ddl/001__schemas.sql: схемы fansy_staging и fansy с грантами - docs/fansy-contract/v1/ddl/002__working.sql: рабочая схема (participants, securities, clients, client_documents, iia_contracts, settlement_requisites, depo_accounts, portfolios, etl_errors) - docs/fansy-contract/v1/ddl/003__staging.sql: staging-зеркало с loaded_at и сниженными ограничениями - docs/fansy-contract/v1/ddl/004__seed_participants.sql: предзаполнение справочника (НРД, БКС 5406121446, Ренессанс 7709258228, Альфа-Банк 7728168971) - docs/fansy-contract/v1/data-dictionary.md: семантика каждого поля - docs/fansy-contract/v1/etl-requirements.md: требования к ETL (UPSERT в staging, SLA свежести по таблицам, обработка ошибок) - docs/fansy-contract/v1/examples/example-claim.md: SQL-запросы для формирования M2MTransferRequest - docs/fansy-contract/v1/examples/seed-data.sql: 5 тестовых клиентов + портфели + договоры - migrations/fansy-store/: рабочие копии миграций Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,33 +1,56 @@
|
|||||||
# docs/fansy-contract/v1 — контракт данных с командой Fansy
|
# docs/fansy-contract/v1 — контракт данных с командой Fansy
|
||||||
|
|
||||||
ETL Fansy → принимающая БД (`fansy-store`) реализует **другая команда
|
ETL Fansy → принимающая БД (`fansy-store`) реализует **другая команда
|
||||||
разработки**. С нашей стороны:
|
разработки**. С нашей стороны зафиксирован контракт: схема таблиц,
|
||||||
|
индексы, миграции, требования к выгрузке и тестовые данные.
|
||||||
|
|
||||||
1. Спроектировать таблицы по требованиям документации НРД к данным M2M.
|
## Состав каталога
|
||||||
2. Передать команде Fansy DDL и контракт данных.
|
|
||||||
3. Согласовать тип load (UPSERT в staging), окна обновления, SLA на
|
|
||||||
свежесть данных.
|
|
||||||
4. Не давать ETL-роли DDL-прав в принимающей схеме.
|
|
||||||
|
|
||||||
Состав каталога (создаём в M1, отправляем в начале M2):
|
- **`ddl/`** — SQL-миграции PostgreSQL:
|
||||||
|
- `000__roles.sql` — роли `fansy_etl` (ETL Fansy), `bj_reader`
|
||||||
|
(наши сервисы), `bj_migrator` (миграции).
|
||||||
|
- `001__schemas.sql` — две схемы: `fansy_staging` (куда пишет ETL) и
|
||||||
|
`fansy` (рабочая, для нашего чтения). Гранты по ролям.
|
||||||
|
- `002__working.sql` — рабочие таблицы: `participants`, `securities`,
|
||||||
|
`clients`, `client_documents`, `iia_contracts`,
|
||||||
|
`settlement_requisites`, `depo_accounts`, `portfolios`,
|
||||||
|
`etl_errors`.
|
||||||
|
- `003__staging.sql` — staging-зеркало рабочих таблиц с полем
|
||||||
|
`loaded_at` и сниженными ограничениями.
|
||||||
|
- `004__seed_participants.sql` — предзаполнение справочника
|
||||||
|
участников: НРД, БКС (5406121446), Ренессанс (7709258228),
|
||||||
|
Альфа-Банк (7728168971).
|
||||||
|
- **`data-dictionary.md`** — семантика каждого поля.
|
||||||
|
- **`etl-requirements.md`** — требования к процессу выгрузки от
|
||||||
|
команды Fansy: подключение, тип load (UPSERT в staging),
|
||||||
|
SLA свежести по таблицам, обработка ошибок, окна простоя, ПДн.
|
||||||
|
- **`examples/`**:
|
||||||
|
- `example-claim.md` — какие данные `m2m-core` тянет из БД для
|
||||||
|
одной типовой M2M-заявки (с конкретными SQL).
|
||||||
|
- `seed-data.sql` — 5 тестовых клиентов, портфели, договоры —
|
||||||
|
основа для приёмочного теста.
|
||||||
|
|
||||||
- `ddl/` — `*.sql` миграции PostgreSQL для всех таблиц.
|
## Рабочие копии миграций
|
||||||
- `data-dictionary.md` — семантика каждого поля (источник в Fansy,
|
|
||||||
nullable, единицы, примеры).
|
|
||||||
- `etl-requirements.md` — требования к процессу выгрузки: тип load,
|
|
||||||
расписание, способ записи, окна простоя, обработка ошибок,
|
|
||||||
конфиденциальность.
|
|
||||||
- `examples/` — пример заявки M2M «end-to-end», 5–10 тестовых клиентов
|
|
||||||
и заявок для совместного приёмочного теста.
|
|
||||||
|
|
||||||
Минимальный набор таблиц (см. план):
|
Те же файлы лежат в `migrations/fansy-store/` — оттуда они
|
||||||
|
применяются при инициализации БД сервиса.
|
||||||
|
|
||||||
- Депоненты / клиенты.
|
## Порядок согласования
|
||||||
- Документы инвестора (`IdentityDocumentCodeEnum`).
|
|
||||||
- ИИС-договоры (`IIAContractTypeEnum ∈ {T12, T03}`).
|
1. Передать команде Fansy ссылку на эту папку (тег `fansy-contract-v1`).
|
||||||
- Депо-счета и разделы (`AccountId`, `SectionId`, `DeponentCode`).
|
2. Обсудить с ними SLA, окна простоя, тип load.
|
||||||
- Реквизиты расчётов (ИНН депозитария).
|
3. По согласовании — дать им учётку с ролью `fansy_etl` и подсеть для
|
||||||
- Портфели и остатки (Whole / Fractional, `IsolationStatus = SGDN`).
|
доступа.
|
||||||
- Справочник ЦБ (`SecurityCode`, `ISIN`, `Classification`, `Category`).
|
4. Запустить совместный приёмочный тест на `seed-data.sql`.
|
||||||
- Контрагенты-участники сервиса MOST (Справочник пользователей).
|
5. Изменения контракта — через новую папку `v2/` с changelog'ом, без
|
||||||
- Audit / staging-таблицы для каждой основной.
|
правки `v1/`.
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
- Имена таблиц/колонок — `snake_case` английский.
|
||||||
|
- Комментарии к таблицам и важным колонкам — на русском
|
||||||
|
через `COMMENT ON ... IS '...'`.
|
||||||
|
- Все timestamp — `timestamptz` в UTC.
|
||||||
|
- DDL-права только у `bj_migrator`, у `fansy_etl` нет.
|
||||||
|
- ETL пишет ТОЛЬКО в `fansy_staging.*`. Перелив в `fansy.*` — на нашей
|
||||||
|
стороне после валидации.
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Data Dictionary — fansy-store v1
|
||||||
|
|
||||||
|
Семантика полей рабочей схемы `fansy`. Структура staging-схемы
|
||||||
|
`fansy_staging` повторяет её один-к-одному, плюс поле `loaded_at` и
|
||||||
|
отсутствие части ограничений (валидация — при переливе).
|
||||||
|
|
||||||
|
Обозначения: `?` — nullable; `!` — обязательное.
|
||||||
|
|
||||||
|
## participants — справочник контрагентов M2M
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| inn | varchar(10) | ! | ИНН юрлица, PK | `client_master.inn` | `7702165310` |
|
||||||
|
| ogrn | varchar(15) | ? | ОГРН | `client_master.ogrn` | `1027739132563` |
|
||||||
|
| full_name_rus | text | ! | Полное наименование на русском | `client_master.full_name` | `НКО АО НРД` |
|
||||||
|
| short_name_rus | text | ? | Короткое наименование | `client_master.short_name` | `НРД` |
|
||||||
|
| display_name_rus | text | ! | Отображаемое имя для UI | `client_master.display_name` | `НРД` |
|
||||||
|
| full_name_eng | text | ? | Полное наименование на английском | `client_master.full_name_en` | `National Settlement Depository` |
|
||||||
|
| short_name_eng | text | ? | Короткое английское | `client_master.short_name_en` | `NSD` |
|
||||||
|
| display_name_eng | text | ? | Английское display | `client_master.display_name_en` | `NSD` |
|
||||||
|
| depository_participant_code | varchar(12) | ? | Код участника M2M (депозитарий) | `m2m_codes.dep_code` | `MC0010300000` |
|
||||||
|
| broker_participant_code | varchar(12) | ? | Код участника M2M (брокер) | `m2m_codes.brk_code` | `MC0079200001` |
|
||||||
|
| is_available_for_m2m | boolean | ! | Готовность к приёму M2M | `m2m_codes.is_active` | `true` |
|
||||||
|
| comment | text | ? | Свободный комментарий | — | — |
|
||||||
|
| created_at, updated_at | timestamptz | ! | Авто | — | — |
|
||||||
|
|
||||||
|
## securities — справочник ЦБ
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| security_code | char(12) | ! | Идентификатор ЦБ в системе НРД, PK | `security_master.nsd_code` | `MM0766162534` |
|
||||||
|
| isin | char(12) | ? | ISIN | `security_master.isin` | `RU0007661625` |
|
||||||
|
| classification | varchar(4) | ? | `BOND` (облигация), `SHAR` (акция), `MFUN` (ПИФ) | `security_master.type_code` | `SHAR` |
|
||||||
|
| category | varchar(4) | ? | `ORDN`/`PREF`/`UKWN` | `security_master.category` | `ORDN` |
|
||||||
|
| security_type | varchar(256) | ? | Текстовое описание типа | `security_master.type_text` | `Акция обыкновенная` |
|
||||||
|
| security_series | text | ? | Серия выпуска (для облигаций) | `security_master.series` | `01` |
|
||||||
|
| reg_number | varchar(256) | ? | Регистрационный номер выпуска / правил ДУ ПИФ | `security_master.reg_number` | `1-01-00010-A` |
|
||||||
|
| fund_class | varchar(120) | ? | Класс паёв ПИФ | `security_master.fund_class` | `A` |
|
||||||
|
| display_name | text | ! | Отображаемое имя для UI | `security_master.display` | `Сбербанк ао` |
|
||||||
|
|
||||||
|
## clients — депоненты-физлица
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK, генерируется БД | `customer.uuid` | — |
|
||||||
|
| inn | varchar(12) | ? | ИНН (10 цифр юрлицо, 12 цифр физлицо) | `customer.inn` | `771234567890` |
|
||||||
|
| last_name | varchar(50) | ! | Фамилия | `customer.last_name` | `Иванов` |
|
||||||
|
| first_name | varchar(50) | ! | Имя | `customer.first_name` | `Иван` |
|
||||||
|
| middle_name | varchar(50) | ? | Отчество | `customer.middle_name` | `Иванович` |
|
||||||
|
| birth_date | date | ? | Дата рождения | `customer.birth_date` | `1980-01-15` |
|
||||||
|
|
||||||
|
## client_documents — документы инвестора
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK | — | — |
|
||||||
|
| client_id | uuid | ! | FK на `clients.id` | `customer_doc.customer_uuid` | — |
|
||||||
|
| document_type | varchar(2) | ! | Код документа по справочнику НРД (01..91) | `customer_doc.type_code` | `21` |
|
||||||
|
| series | text | ? | Серия (без пробелов) | `customer_doc.series` | `4512` |
|
||||||
|
| number | text | ! | Номер (без пробелов) | `customer_doc.number` | `654321` |
|
||||||
|
| issued_at | date | ? | Дата выдачи | `customer_doc.issued_at` | `2010-05-12` |
|
||||||
|
| issuer | text | ? | Кем выдан | `customer_doc.issuer` | `ОУФМС России` |
|
||||||
|
|
||||||
|
## iia_contracts — договоры ИИС
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK | — | — |
|
||||||
|
| client_id | uuid | ! | FK на `clients.id` | — | — |
|
||||||
|
| agreement_type | varchar(3) | ! | `T12` (ИИС-1/ИИС-2) или `T03` (ИИС-3) | `iia.type` | `T03` |
|
||||||
|
| agreement_number | varchar(128) | ! | Номер договора | `iia.number` | `ИИС78/2024` |
|
||||||
|
| agreement_date | date | ! | Дата заключения | `iia.signed_at` | `2026-01-15` |
|
||||||
|
| broker_inn | varchar(10) | ! | ИНН брокера, ведущего ИИС | `iia.broker_inn` | `0707083893` |
|
||||||
|
|
||||||
|
## settlement_requisites — реквизиты депозитариев
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK |
|
||||||
|
| inn | varchar(10) | ! | ИНН депозитария, UNIQUE |
|
||||||
|
| display_name | text | ! | Отображаемое имя |
|
||||||
|
|
||||||
|
## depo_accounts — счета депо
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK | — | — |
|
||||||
|
| client_id | uuid | ! | FK на `clients.id` | — | — |
|
||||||
|
| deponent_code | varchar(50) | ! | Код депонента у депозитария | `depo.deponent_code` | `DP789456` |
|
||||||
|
| account_id | varchar(50) | ! | Номер счёта депо | `depo.account_id` | `31MC0021900000F01` |
|
||||||
|
| section_id | varchar(50) | ! | Номер раздела счёта | `depo.section_id` | `P001` |
|
||||||
|
| depository_inn | varchar(10) | ! | ИНН депозитария | `depo.depository_inn` | `7702070139` |
|
||||||
|
| is_active | boolean | ! | Активен ли счёт | `depo.is_active` | `true` |
|
||||||
|
| is_trading | boolean | ! | Торговый раздел | `depo.is_trading` | `true` |
|
||||||
|
|
||||||
|
Уникальность по тройке `(deponent_code, account_id, section_id)`.
|
||||||
|
|
||||||
|
## portfolios — портфели и остатки ЦБ
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание | Источник Fansy | Пример |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| id | uuid | ! | PK | — | — |
|
||||||
|
| client_id | uuid | ! | FK на `clients.id` | — | — |
|
||||||
|
| depo_account_id | uuid | ! | FK на `depo_accounts.id` | — | — |
|
||||||
|
| security_code | char(12) | ! | FK на `securities.security_code` | — | `MM0766162534` |
|
||||||
|
| isin | char(12) | ? | Кэш ISIN из securities | — | `RU0007661625` |
|
||||||
|
| quantity_whole | numeric(38,0) | ? | Целое количество (для акций/облигаций) | `position.qty_whole` | `1500` |
|
||||||
|
| quantity_fractional | numeric(38,16) | ? | Дробное (для паёв) | `position.qty_fract` | `2500.7500000000000000` |
|
||||||
|
| isolation_status | varchar(4) | ! | Всегда `SGDN` | — | `SGDN` |
|
||||||
|
| valued_at | timestamptz | ! | На какой момент актуально | `position.valued_at` | `2026-03-02T11:30:00Z` |
|
||||||
|
|
||||||
|
Должно быть заполнено ровно одно из (`quantity_whole`, `quantity_fractional`).
|
||||||
|
|
||||||
|
## etl_errors — журнал ошибок ETL
|
||||||
|
|
||||||
|
| Поле | Тип | Обяз. | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| id | bigserial | ! | PK |
|
||||||
|
| source_table | text | ! | Таблица в Fansy |
|
||||||
|
| source_pk | text | ? | PK записи в Fansy |
|
||||||
|
| payload | jsonb | ? | Сама запись для ретрая |
|
||||||
|
| error_message | text | ! | Сообщение об ошибке |
|
||||||
|
| created_at | timestamptz | ! | Когда зафиксирована |
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
-- 000__roles.sql
|
||||||
|
-- Роли для принимающей БД fansy-store.
|
||||||
|
-- Запускать первым, отдельно от структурных миграций.
|
||||||
|
-- Пароли проставляются администратором БД через ALTER ROLE.
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fansy_etl') THEN
|
||||||
|
CREATE ROLE fansy_etl LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE fansy_etl IS
|
||||||
|
'Роль команды Fansy для ETL: INSERT/UPDATE/SELECT в схему fansy_staging. DDL-прав нет.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bj_reader') THEN
|
||||||
|
CREATE ROLE bj_reader LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE bj_reader IS
|
||||||
|
'Роль сервисов Bridge-and-Join-s (m2m-core, lk-gateway) для чтения схемы fansy.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bj_migrator') THEN
|
||||||
|
CREATE ROLE bj_migrator LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE bj_migrator IS
|
||||||
|
'Роль с DDL-правами для миграций. Только эта роль может CREATE/ALTER/DROP.';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- 001__schemas.sql
|
||||||
|
-- Две схемы: fansy_staging (куда пишет ETL Fansy) и fansy (рабочая,
|
||||||
|
-- куда переливаются данные после валидации).
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS fansy_staging AUTHORIZATION bj_migrator;
|
||||||
|
COMMENT ON SCHEMA fansy_staging IS
|
||||||
|
'Staging-схема. ETL Fansy делает UPSERT в эти таблицы. Сюда же пишутся ошибки выгрузки.';
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS fansy AUTHORIZATION bj_migrator;
|
||||||
|
COMMENT ON SCHEMA fansy IS
|
||||||
|
'Рабочая схема. Сюда переливаются актуальные данные триггерами или процедурами после валидации staging.';
|
||||||
|
|
||||||
|
-- Права по ролям. DDL-права остаются только у владельца bj_migrator.
|
||||||
|
GRANT USAGE ON SCHEMA fansy_staging TO fansy_etl;
|
||||||
|
GRANT USAGE ON SCHEMA fansy TO bj_reader;
|
||||||
|
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy_staging
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON TABLES TO fansy_etl;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy_staging
|
||||||
|
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO fansy_etl;
|
||||||
|
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy
|
||||||
|
GRANT SELECT ON TABLES TO bj_reader;
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
-- 002__working.sql
|
||||||
|
-- Рабочая схема fansy. Данные сюда переливаются из fansy_staging после
|
||||||
|
-- валидации. Сервисы Bridge-and-Join-s читают только эту схему.
|
||||||
|
|
||||||
|
SET search_path TO fansy, public;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- participants — справочник участников сервиса MOST (контрагенты M2M)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS participants (
|
||||||
|
inn varchar(10) PRIMARY KEY,
|
||||||
|
ogrn varchar(15),
|
||||||
|
full_name_rus text NOT NULL,
|
||||||
|
short_name_rus text,
|
||||||
|
display_name_rus text NOT NULL,
|
||||||
|
full_name_eng text,
|
||||||
|
short_name_eng text,
|
||||||
|
display_name_eng text,
|
||||||
|
depository_participant_code varchar(12),
|
||||||
|
broker_participant_code varchar(12),
|
||||||
|
is_available_for_m2m boolean NOT NULL DEFAULT false,
|
||||||
|
comment text,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn ~ '^[0-9]{10}$'),
|
||||||
|
CHECK (depository_participant_code IS NULL OR depository_participant_code ~ '^[A-Z0-9]+$'),
|
||||||
|
CHECK (broker_participant_code IS NULL OR broker_participant_code ~ '^[A-Z0-9]+$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE participants IS 'Справочник участников сервиса MOST: депозитарии и брокеры, между которыми идут M2M-переводы.';
|
||||||
|
COMMENT ON COLUMN participants.inn IS 'ИНН юрлица (10 цифр), первичный ключ.';
|
||||||
|
COMMENT ON COLUMN participants.depository_participant_code IS 'Код участника M2M на стороне депозитария (для DepositoryPlace в M2MTransferHandbook).';
|
||||||
|
COMMENT ON COLUMN participants.broker_participant_code IS 'Код участника M2M на стороне брокера (для BrokerPlace).';
|
||||||
|
COMMENT ON COLUMN participants.is_available_for_m2m IS 'Готовность участника принимать/отправлять M2M-сообщения (включается после подписания НРД-договора).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_participants_dep_code ON participants(depository_participant_code) WHERE depository_participant_code IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_participants_brk_code ON participants(broker_participant_code) WHERE broker_participant_code IS NOT NULL;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- securities — справочник ценных бумаг
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS securities (
|
||||||
|
security_code char(12) PRIMARY KEY,
|
||||||
|
isin char(12),
|
||||||
|
classification varchar(4),
|
||||||
|
category varchar(4),
|
||||||
|
security_type varchar(256),
|
||||||
|
security_series text,
|
||||||
|
reg_number varchar(256),
|
||||||
|
fund_class varchar(120),
|
||||||
|
display_name text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (security_code ~ '^[0-9A-Z_/-]+$'),
|
||||||
|
CHECK (isin IS NULL OR isin ~ '^[A-Z]{2}[A-Z0-9]{9}[0-9]$'),
|
||||||
|
CHECK (classification IS NULL OR classification IN ('BOND', 'SHAR', 'MFUN')),
|
||||||
|
CHECK (category IS NULL OR category IN ('ORDN', 'PREF', 'UKWN'))
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE securities IS 'Справочник ценных бумаг с их идентификаторами и классификацией.';
|
||||||
|
COMMENT ON COLUMN securities.security_code IS 'Идентификатор ценной бумаги в системе НРД (XSD SecurityCodeType).';
|
||||||
|
COMMENT ON COLUMN securities.classification IS 'Тип ценной бумаги: BOND (облигация), SHAR (акция), MFUN (ПИФ).';
|
||||||
|
COMMENT ON COLUMN securities.category IS 'Категория акций: ORDN (обыкновенные), PREF (привилегированные), UKWN (неизвестно).';
|
||||||
|
COMMENT ON COLUMN securities.reg_number IS 'Регистрационный номер выпуска (для акций и облигаций) или регномер правил доверительного управления ПИФ.';
|
||||||
|
COMMENT ON COLUMN securities.fund_class IS 'Класс паёв ПИФа (если применимо).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_securities_isin ON securities(isin) WHERE isin IS NOT NULL;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- clients — депоненты / инвесторы
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
inn varchar(12),
|
||||||
|
last_name varchar(50) NOT NULL,
|
||||||
|
first_name varchar(50) NOT NULL,
|
||||||
|
middle_name varchar(50),
|
||||||
|
birth_date date,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn IS NULL OR inn ~ '^[0-9]{10,12}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE clients IS 'Депоненты-физлица. Привязка к документам и счетам — через FK из дочерних таблиц.';
|
||||||
|
COMMENT ON COLUMN clients.inn IS 'ИНН физлица (12 цифр) или организации (10 цифр), опционально.';
|
||||||
|
COMMENT ON COLUMN clients.last_name IS 'Фамилия (XSD String50, обязательно).';
|
||||||
|
COMMENT ON COLUMN clients.first_name IS 'Имя (XSD String50, обязательно).';
|
||||||
|
COMMENT ON COLUMN clients.middle_name IS 'Отчество (XSD String50, опционально).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clients_inn ON clients(inn) WHERE inn IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clients_lastname ON clients(last_name, first_name);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- client_documents — документы инвестора
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_documents (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
document_type varchar(2) NOT NULL,
|
||||||
|
series text,
|
||||||
|
number text NOT NULL,
|
||||||
|
issued_at date,
|
||||||
|
issuer text,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (document_type IN (
|
||||||
|
'01','02','03','04','05','06','07','09','10','11','12','13','14',
|
||||||
|
'21','22','23','26','27','91'
|
||||||
|
)),
|
||||||
|
CHECK (series IS NULL OR series ~ '^\S+$'),
|
||||||
|
CHECK (number ~ '^\S+$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE client_documents IS 'Документы, удостоверяющие личность инвестора. Коды по справочнику НРД (XSD IdentityDocumentCodeEnum).';
|
||||||
|
COMMENT ON COLUMN client_documents.document_type IS 'Код вида документа (01..91, см. XSD НРД).';
|
||||||
|
COMMENT ON COLUMN client_documents.series IS 'Серия документа (без пробелов).';
|
||||||
|
COMMENT ON COLUMN client_documents.number IS 'Номер документа (без пробелов, обязательно).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_client_documents_client ON client_documents(client_id);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- iia_contracts — договоры ИИС
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS iia_contracts (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
agreement_type varchar(3) NOT NULL,
|
||||||
|
agreement_number varchar(128) NOT NULL,
|
||||||
|
agreement_date date NOT NULL,
|
||||||
|
broker_inn varchar(10) NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (agreement_type IN ('T12', 'T03')),
|
||||||
|
CHECK (broker_inn ~ '^[0-9]{10}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE iia_contracts IS 'Договоры на ведение ИИС инвестора.';
|
||||||
|
COMMENT ON COLUMN iia_contracts.agreement_type IS 'Тип договора: T12 — ИИС-1/ИИС-2 (старый формат); T03 — ИИС-3 (новый).';
|
||||||
|
COMMENT ON COLUMN iia_contracts.broker_inn IS 'ИНН брокера, с которым заключён договор ИИС.';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_iia_contracts_client ON iia_contracts(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_iia_contracts_broker ON iia_contracts(broker_inn);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- settlement_requisites — реквизиты расчётов (депозитарии)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS settlement_requisites (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
inn varchar(10) NOT NULL UNIQUE,
|
||||||
|
display_name text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn ~ '^[0-9]{10}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE settlement_requisites IS 'Реквизиты передающего и принимающего депозитариев (XSD SettlementRequisitesType — содержит только ИНН).';
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- depo_accounts — депо-счета и разделы
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS depo_accounts (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
|
||||||
|
deponent_code varchar(50) NOT NULL,
|
||||||
|
account_id varchar(50) NOT NULL,
|
||||||
|
section_id varchar(50) NOT NULL,
|
||||||
|
depository_inn varchar(10) NOT NULL,
|
||||||
|
is_active boolean NOT NULL DEFAULT true,
|
||||||
|
is_trading boolean NOT NULL DEFAULT false,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (depository_inn ~ '^[0-9]{10}$'),
|
||||||
|
UNIQUE (deponent_code, account_id, section_id)
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE depo_accounts IS 'Счета депо инвестора и их разделы у различных депозитариев.';
|
||||||
|
COMMENT ON COLUMN depo_accounts.deponent_code IS 'Код депонента у конкретного депозитария (XSD SettlementDepositoryLocationType.DeponentCode).';
|
||||||
|
COMMENT ON COLUMN depo_accounts.account_id IS 'Номер счёта депо (XSD AccountIdType).';
|
||||||
|
COMMENT ON COLUMN depo_accounts.section_id IS 'Номер раздела счёта депо.';
|
||||||
|
COMMENT ON COLUMN depo_accounts.is_trading IS 'Признак торгового раздела (для отделения от изолированных).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_depo_accounts_client ON depo_accounts(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_depo_accounts_deponent ON depo_accounts(deponent_code);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- portfolios — портфели и остатки ЦБ
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portfolios (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
depo_account_id uuid NOT NULL REFERENCES depo_accounts(id) ON DELETE CASCADE,
|
||||||
|
security_code char(12) NOT NULL REFERENCES securities(security_code),
|
||||||
|
isin char(12),
|
||||||
|
quantity_whole numeric(38, 0),
|
||||||
|
quantity_fractional numeric(38, 16),
|
||||||
|
isolation_status varchar(4) NOT NULL DEFAULT 'SGDN',
|
||||||
|
valued_at timestamptz NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (isolation_status IN ('SGDN')),
|
||||||
|
CHECK ((quantity_whole IS NOT NULL) OR (quantity_fractional IS NOT NULL)),
|
||||||
|
CHECK (isin IS NULL OR isin ~ '^[A-Z]{2}[A-Z0-9]{9}[0-9]$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE portfolios IS 'Остатки ценных бумаг на счетах депо. Whole/Fractional — choice по XSD QuantityType (заполняется ровно одно).';
|
||||||
|
COMMENT ON COLUMN portfolios.quantity_whole IS 'Целое количество (для акций, облигаций).';
|
||||||
|
COMMENT ON COLUMN portfolios.quantity_fractional IS 'Дробное количество (для паёв ПИФ, до 16 знаков после точки).';
|
||||||
|
COMMENT ON COLUMN portfolios.isolation_status IS 'Статус обособления по XSD НРД, всегда SGDN.';
|
||||||
|
COMMENT ON COLUMN portfolios.valued_at IS 'Дата/время оценки (на какой момент актуален остаток).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_client ON portfolios(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_depo ON portfolios(depo_account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_security ON portfolios(security_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_valued_at ON portfolios(valued_at DESC);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- etl_errors — ошибки выгрузки Fansy
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS etl_errors (
|
||||||
|
id bigserial PRIMARY KEY,
|
||||||
|
source_table text NOT NULL,
|
||||||
|
source_pk text,
|
||||||
|
payload jsonb,
|
||||||
|
error_message text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE etl_errors IS 'Журнал ошибок выгрузки Fansy: что не смогли записать в staging и почему.';
|
||||||
|
COMMENT ON COLUMN etl_errors.source_table IS 'Название таблицы в источнике (Fansy).';
|
||||||
|
COMMENT ON COLUMN etl_errors.source_pk IS 'Первичный ключ записи в источнике (для повторной попытки).';
|
||||||
|
COMMENT ON COLUMN etl_errors.payload IS 'Сама запись, которую не удалось загрузить (для диагностики).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_etl_errors_created ON etl_errors(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_etl_errors_table ON etl_errors(source_table);
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
-- 003__staging.sql
|
||||||
|
-- Staging-схема. Структура повторяет fansy.*, плюс loaded_at и
|
||||||
|
-- допущения на промежуточные NULL'ы (валидация будет в процессе
|
||||||
|
-- перелива в fansy.*).
|
||||||
|
|
||||||
|
SET search_path TO fansy_staging, public;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS participants (
|
||||||
|
inn varchar(10) PRIMARY KEY,
|
||||||
|
ogrn varchar(15),
|
||||||
|
full_name_rus text,
|
||||||
|
short_name_rus text,
|
||||||
|
display_name_rus text,
|
||||||
|
full_name_eng text,
|
||||||
|
short_name_eng text,
|
||||||
|
display_name_eng text,
|
||||||
|
depository_participant_code varchar(12),
|
||||||
|
broker_participant_code varchar(12),
|
||||||
|
is_available_for_m2m boolean,
|
||||||
|
comment text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE participants IS 'Staging для справочника участников. Перезаливка целиком, не чаще раза в сутки.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS securities (
|
||||||
|
security_code char(12) PRIMARY KEY,
|
||||||
|
isin char(12),
|
||||||
|
classification varchar(4),
|
||||||
|
category varchar(4),
|
||||||
|
security_type varchar(256),
|
||||||
|
security_series text,
|
||||||
|
reg_number varchar(256),
|
||||||
|
fund_class varchar(120),
|
||||||
|
display_name text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE securities IS 'Staging для справочника ЦБ. Перезаливка целиком.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
inn varchar(12),
|
||||||
|
last_name varchar(50),
|
||||||
|
first_name varchar(50),
|
||||||
|
middle_name varchar(50),
|
||||||
|
birth_date date,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE clients IS 'Staging для клиентов. Инкрементный UPSERT по id.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_documents (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
document_type varchar(2),
|
||||||
|
series text,
|
||||||
|
number text,
|
||||||
|
issued_at date,
|
||||||
|
issuer text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE client_documents IS 'Staging для документов клиента. UPSERT по id.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS iia_contracts (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
agreement_type varchar(3),
|
||||||
|
agreement_number varchar(128),
|
||||||
|
agreement_date date,
|
||||||
|
broker_inn varchar(10),
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE iia_contracts IS 'Staging для договоров ИИС.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS settlement_requisites (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
inn varchar(10) NOT NULL,
|
||||||
|
display_name text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE settlement_requisites IS 'Staging для реквизитов расчётов.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS depo_accounts (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
deponent_code varchar(50),
|
||||||
|
account_id varchar(50),
|
||||||
|
section_id varchar(50),
|
||||||
|
depository_inn varchar(10),
|
||||||
|
is_active boolean,
|
||||||
|
is_trading boolean,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE depo_accounts IS 'Staging для депо-счетов.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portfolios (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
depo_account_id uuid NOT NULL,
|
||||||
|
security_code char(12) NOT NULL,
|
||||||
|
isin char(12),
|
||||||
|
quantity_whole numeric(38, 0),
|
||||||
|
quantity_fractional numeric(38, 16),
|
||||||
|
isolation_status varchar(4),
|
||||||
|
valued_at timestamptz,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE portfolios IS 'Staging для портфелей. UPSERT по id; SLA свежести — 1 мин.';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stg_portfolios_loaded ON portfolios(loaded_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stg_clients_loaded ON clients(loaded_at DESC);
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
-- 004__seed_participants.sql
|
||||||
|
-- Предзаполнение справочника участников по DOC/Справочник пользователей.pdf
|
||||||
|
-- НРД и тестовые контрагенты Регламента M2M.
|
||||||
|
|
||||||
|
SET search_path TO fansy, public;
|
||||||
|
|
||||||
|
INSERT INTO participants (
|
||||||
|
inn, ogrn, full_name_rus, short_name_rus, display_name_rus,
|
||||||
|
full_name_eng, short_name_eng, display_name_eng,
|
||||||
|
depository_participant_code, broker_participant_code,
|
||||||
|
is_available_for_m2m, comment
|
||||||
|
) VALUES
|
||||||
|
(
|
||||||
|
'7702165310', '1027739132563',
|
||||||
|
'Небанковская кредитная организация акционерное общество "Национальный расчётный депозитарий"',
|
||||||
|
'НКО АО НРД', 'НРД',
|
||||||
|
'National Settlement Depository', 'NSD', 'NSD',
|
||||||
|
'MC0010300000', NULL, true,
|
||||||
|
'Центральный депозитарий, держатель реестра M2M-сделок.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'5406121446', '1025402459334',
|
||||||
|
'Общество с ограниченной ответственностью "Компания БКС"',
|
||||||
|
'ООО "Компания БКС"', 'БКС',
|
||||||
|
'BCS Company Ltd', 'BCS', 'BCS',
|
||||||
|
NULL, 'MC0079200001', true,
|
||||||
|
'Брокер БКС, контрагент M2M.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'7709258228', '1027739675260',
|
||||||
|
'Общество с ограниченной ответственностью "Ренессанс Брокер"',
|
||||||
|
'ООО "Ренессанс Брокер"', 'Ренессанс Брокер',
|
||||||
|
'Renaissance Broker Ltd', 'Renaissance', 'Renaissance',
|
||||||
|
NULL, 'MC0010300032', true,
|
||||||
|
'Брокер Ренессанс, контрагент M2M.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'7728168971', '1027700067328',
|
||||||
|
'Акционерное общество "Альфа-Банк"',
|
||||||
|
'АО "Альфа-Банк"', 'Альфа-Банк',
|
||||||
|
'Alfa-Bank JSC', 'Alfa-Bank', 'Alfa-Bank',
|
||||||
|
NULL, 'MC0079200033', true,
|
||||||
|
'Брокер Альфа-Банк, контрагент M2M.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (inn) DO UPDATE SET
|
||||||
|
full_name_rus = EXCLUDED.full_name_rus,
|
||||||
|
short_name_rus = EXCLUDED.short_name_rus,
|
||||||
|
display_name_rus = EXCLUDED.display_name_rus,
|
||||||
|
full_name_eng = EXCLUDED.full_name_eng,
|
||||||
|
short_name_eng = EXCLUDED.short_name_eng,
|
||||||
|
display_name_eng = EXCLUDED.display_name_eng,
|
||||||
|
depository_participant_code = EXCLUDED.depository_participant_code,
|
||||||
|
broker_participant_code = EXCLUDED.broker_participant_code,
|
||||||
|
is_available_for_m2m = EXCLUDED.is_available_for_m2m,
|
||||||
|
comment = EXCLUDED.comment,
|
||||||
|
updated_at = now();
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Требования к ETL Fansy → fansy-store v1
|
||||||
|
|
||||||
|
## Подключение
|
||||||
|
|
||||||
|
- СУБД: PostgreSQL 16 / PostgreSQL Pro Certified (по согласованию).
|
||||||
|
- Хост, порт, имя БД, IP-allowlist — выдаются администратором ВМ
|
||||||
|
Bridge-and-Join-s отдельно.
|
||||||
|
- Учётная запись: роль **`fansy_etl`** (создаётся миграцией
|
||||||
|
`000__roles.sql`). Пароль выдаётся через защищённый канал, не в
|
||||||
|
репозиторий.
|
||||||
|
- TLS: обязательно (`sslmode=verify-full` со стороны клиента ETL).
|
||||||
|
|
||||||
|
## Куда писать
|
||||||
|
|
||||||
|
- Только в схему `fansy_staging`. Прав на DDL нет, на схему `fansy`
|
||||||
|
тоже нет. INSERT/UPDATE/SELECT на таблицы staging.
|
||||||
|
- Запись в `fansy.*` происходит на нашей стороне после валидации.
|
||||||
|
|
||||||
|
## Тип load
|
||||||
|
|
||||||
|
- **Инкрементный UPSERT** в staging по PK (`id`):
|
||||||
|
```sql
|
||||||
|
INSERT INTO fansy_staging.clients (id, ...) VALUES (...)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET ..., loaded_at = now();
|
||||||
|
```
|
||||||
|
- Справочники с относительно небольшим размером и редкой сменой
|
||||||
|
(`securities`, `participants`) разрешена **полная перезаливка** не
|
||||||
|
чаще одного раза в сутки. Полная перезаливка реализуется через
|
||||||
|
транзакцию: `TRUNCATE` + `COPY` + `COMMIT`.
|
||||||
|
|
||||||
|
## SLA на свежесть данных
|
||||||
|
|
||||||
|
| Таблица | SLA свежести |
|
||||||
|
|---|---|
|
||||||
|
| `portfolios` | ≤ 1 минута после фактического изменения в Fansy |
|
||||||
|
| `clients`, `depo_accounts`, `client_documents`, `iia_contracts` | ≤ 5 минут |
|
||||||
|
| `securities`, `participants`, `settlement_requisites` | ≤ 24 часа (по событию или по расписанию) |
|
||||||
|
|
||||||
|
## Форматы и кодировки
|
||||||
|
|
||||||
|
- Все timestamp — `timestamptz` в **UTC** (явная зона `+00`).
|
||||||
|
- Все строковые поля — UTF-8.
|
||||||
|
- ИНН, коды депонентов, ISIN, SecurityCode — в верхнем регистре.
|
||||||
|
- Числа с дробной частью (`numeric(38,16)`) — точка как разделитель,
|
||||||
|
без разделителей тысяч.
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
При нарушении CHECK-ограничений, FK или типов команда Fansy:
|
||||||
|
|
||||||
|
1. Пишет запись в `fansy_staging.etl_errors`:
|
||||||
|
```sql
|
||||||
|
INSERT INTO fansy_staging.etl_errors (source_table, source_pk, payload, error_message)
|
||||||
|
VALUES ('fansy.position', '<pk>', '<json>', '<text>');
|
||||||
|
```
|
||||||
|
2. Логирует у себя и продолжает работу.
|
||||||
|
3. Не блокирует загрузку остальных записей.
|
||||||
|
|
||||||
|
Мы (Bridge-and-Join-s) еженедельно просматриваем `etl_errors`,
|
||||||
|
поднимаем инциденты с командой Fansy.
|
||||||
|
|
||||||
|
## Окна и расписание
|
||||||
|
|
||||||
|
- Регламентное окно простоя — **с 23:00 до 23:30 МСК**, по средам.
|
||||||
|
В это время ETL может приостанавливаться для обновлений.
|
||||||
|
- Внеплановые работы — анонсируются за 2 часа в общем чате.
|
||||||
|
|
||||||
|
## Конфиденциальность
|
||||||
|
|
||||||
|
- ПДн (ФИО, документ, дата рождения) — только по нужным таблицам.
|
||||||
|
- Журналирование SQL-запросов ETL **не должно** включать значения ПДн.
|
||||||
|
- Соединения только с IP-allowlist'а.
|
||||||
|
|
||||||
|
## Контроль и наблюдаемость
|
||||||
|
|
||||||
|
Мы предоставим команде Fansy `read-only` доступ к двум представлениям:
|
||||||
|
|
||||||
|
- `fansy_staging.v_load_lag` — задержка свежести по таблицам.
|
||||||
|
- `fansy_staging.v_load_stats` — счётчики INSERT/UPDATE за сутки.
|
||||||
|
|
||||||
|
(Создаются в более позднем PR — `M3`.)
|
||||||
|
|
||||||
|
## Точка контакта
|
||||||
|
|
||||||
|
- Технический контакт со стороны Bridge-and-Join-s — указан в
|
||||||
|
`docs/architecture/plan.md`, раздел «Контакты».
|
||||||
|
- Эскалация — в общий канал интеграции, тред «fansy-store ETL».
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Пример заявки M2M end-to-end
|
||||||
|
|
||||||
|
Типовой сценарий: инвестор Иванов И.И. подаёт через ЛК заявку на
|
||||||
|
перевод 3 ценных бумаг с депо-счёта у БКС в депо-счёт у Ренессанс
|
||||||
|
Брокера. Один из переводов — паи ПИФ с дробным количеством. ИИС
|
||||||
|
тип T03.
|
||||||
|
|
||||||
|
## Какие данные нужны m2m-core для формирования M2MTransferRequest
|
||||||
|
|
||||||
|
Сервис `m2m-core` достаёт следующее из `fansy-store` (рабочая схема
|
||||||
|
`fansy`) по идентификатору клиента и набору ЦБ:
|
||||||
|
|
||||||
|
### 1. Анкета клиента (для `InvestorInformation`)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
c.last_name,
|
||||||
|
c.first_name,
|
||||||
|
c.middle_name,
|
||||||
|
d.document_type,
|
||||||
|
d.series AS document_series,
|
||||||
|
d.number AS document_number
|
||||||
|
FROM fansy.clients c
|
||||||
|
JOIN fansy.client_documents d ON d.client_id = c.id
|
||||||
|
WHERE c.id = :client_id
|
||||||
|
ORDER BY d.created_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. ИИС-договор (для `IIAAgreementDetails`)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT agreement_type, agreement_number, agreement_date, broker_inn
|
||||||
|
FROM fansy.iia_contracts
|
||||||
|
WHERE client_id = :client_id
|
||||||
|
ORDER BY agreement_date DESC
|
||||||
|
LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Реквизиты передающего/принимающего депозитариев
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT inn
|
||||||
|
FROM fansy.settlement_requisites
|
||||||
|
WHERE inn IN (:transferring_inn, :receiving_inn);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Депо-счета и разделы инвестора (для `SettlementAccount`)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
da.deponent_code,
|
||||||
|
da.account_id,
|
||||||
|
da.section_id,
|
||||||
|
da.depository_inn
|
||||||
|
FROM fansy.depo_accounts da
|
||||||
|
WHERE da.client_id = :client_id
|
||||||
|
AND da.depository_inn = :depository_inn
|
||||||
|
AND da.is_active = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Информация о ценных бумагах и их остатках
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
p.security_code,
|
||||||
|
s.isin,
|
||||||
|
s.classification,
|
||||||
|
s.category,
|
||||||
|
s.security_type,
|
||||||
|
s.reg_number,
|
||||||
|
s.fund_class,
|
||||||
|
p.quantity_whole,
|
||||||
|
p.quantity_fractional,
|
||||||
|
p.isolation_status
|
||||||
|
FROM fansy.portfolios p
|
||||||
|
JOIN fansy.securities s USING (security_code)
|
||||||
|
WHERE p.client_id = :client_id
|
||||||
|
AND p.security_code = ANY(:requested_codes)
|
||||||
|
AND p.valued_at >= now() - interval '5 minutes';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Проверка достаточности остатков
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
p.security_code,
|
||||||
|
COALESCE(p.quantity_whole, 0) + COALESCE(p.quantity_fractional, 0) AS available
|
||||||
|
FROM fansy.portfolios p
|
||||||
|
WHERE p.client_id = :client_id
|
||||||
|
AND p.security_code = ANY(:requested_codes);
|
||||||
|
```
|
||||||
|
|
||||||
|
Сравниваем `available` с запрошенным количеством. Если меньше — отказ
|
||||||
|
от формирования M2MTransferRequest, ошибка в ЛК.
|
||||||
|
|
||||||
|
## Какие данные команда Fansy обязана положить в staging
|
||||||
|
|
||||||
|
Из примера выше:
|
||||||
|
|
||||||
|
- `clients`: запись на инвестора Иванова И.И.
|
||||||
|
- `client_documents`: документ с DocumentType `21`.
|
||||||
|
- `iia_contracts`: договор T03 с брокером (БКС, ИНН 5406121446).
|
||||||
|
- `depo_accounts`: счёт у БКС с разделом для перевода и счёт у
|
||||||
|
Ренессанс Брокера.
|
||||||
|
- `securities`: 3 записи (SHAR/ORDN, SHAR/PREF, MFUN/UKWN с
|
||||||
|
fund_class='A').
|
||||||
|
- `portfolios`: остатки по этим 3 ЦБ на 1500 / 300 / 2500.75
|
||||||
|
соответственно.
|
||||||
|
- `participants`: НРД, БКС (5406121446), Ренессанс (7709258228) — из
|
||||||
|
начального seed.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
`m2m-core` собирает данные → формирует `M2MTransferRequest` →
|
||||||
|
валидирует → подписывает (через `crypto-service`) → отправляет в НРД
|
||||||
|
через `nsd-adapter`. Получает `M2MTransferDecision` от принимающей
|
||||||
|
стороны, обновляет статус сделки и шлёт callback в ЛК.
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
-- seed-data.sql
|
||||||
|
-- Тестовые данные для совместного приёмочного тестирования
|
||||||
|
-- Bridge-and-Join-s ↔ команда Fansy. Запускать поверх 002__working.sql.
|
||||||
|
|
||||||
|
SET search_path TO fansy, public;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- Реквизиты депозитариев
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO settlement_requisites (id, inn, display_name) VALUES
|
||||||
|
('00000000-0000-0000-0000-000000000001', '7702070139', 'Депозитарий Сбербанк'),
|
||||||
|
('00000000-0000-0000-0000-000000000002', '7802031669', 'Депозитарий СПб Банк'),
|
||||||
|
('00000000-0000-0000-0000-000000000003', '0702345678', 'Депозитарий БКС'),
|
||||||
|
('00000000-0000-0000-0000-000000000004', '0710987654', 'Депозитарий Ренессанс')
|
||||||
|
ON CONFLICT (inn) DO NOTHING;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- Справочник ЦБ (минимальный)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO securities (security_code, isin, classification, category, security_type, reg_number, display_name) VALUES
|
||||||
|
('MM0766162534', 'RU0007661625', 'SHAR', 'ORDN', 'Акция обыкновенная', '1-01-00077-A', 'Газпром ао'),
|
||||||
|
('MM0907654321', 'RU0009029540', 'SHAR', 'PREF', 'Акция привилегированная', '2-02-00009-A', 'Сбербанк ап'),
|
||||||
|
('MM2300100100', NULL, 'MFUN', 'UKWN', 'Пай ПИФ', '23-001', 'ПИФ Альфа Капитал')
|
||||||
|
ON CONFLICT (security_code) DO NOTHING;
|
||||||
|
|
||||||
|
UPDATE securities SET fund_class = 'A' WHERE security_code = 'MM2300100100';
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- 5 тестовых клиентов
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO clients (id, last_name, first_name, middle_name, birth_date) VALUES
|
||||||
|
('11111111-1111-1111-1111-111111111111', 'Иванов', 'Иван', 'Иванович', '1980-01-15'),
|
||||||
|
('22222222-2222-2222-2222-222222222222', 'Петров', 'Пётр', 'Петрович', '1985-06-20'),
|
||||||
|
('33333333-3333-3333-3333-333333333333', 'Сидоров', 'Сидор', 'Сидорович', '1990-11-30'),
|
||||||
|
('44444444-4444-4444-4444-444444444444', 'Кузнецов','Сергей','Михайлович','1975-03-10'),
|
||||||
|
('55555555-5555-5555-5555-555555555555', 'Соколова','Анна', 'Викторовна','1988-09-25')
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- Документы клиентов
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO client_documents (id, client_id, document_type, series, number, issued_at, issuer) VALUES
|
||||||
|
('a0000000-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111', '21', '4512', '654321', '2010-05-12', 'ОУФМС России по Москве'),
|
||||||
|
('a0000000-0000-0000-0000-000000000002', '22222222-2222-2222-2222-222222222222', '21', '4513', '654322', '2011-06-13', 'ОУФМС России по Москве'),
|
||||||
|
('a0000000-0000-0000-0000-000000000003', '33333333-3333-3333-3333-333333333333', '21', '4514', '654323', '2012-07-14', 'ОУФМС России по СПб'),
|
||||||
|
('a0000000-0000-0000-0000-000000000004', '44444444-4444-4444-4444-444444444444', '03', '111', '222333', '1995-08-15', 'Свидетельство о рождении'),
|
||||||
|
('a0000000-0000-0000-0000-000000000005', '55555555-5555-5555-5555-555555555555', '21', '4516', '654325', '2014-09-16', 'ОУФМС России по СПб')
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- ИИС-договоры (для 3 клиентов)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO iia_contracts (id, client_id, agreement_type, agreement_number, agreement_date, broker_inn) VALUES
|
||||||
|
('b0000000-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111', 'T03', 'ИИС78/2024', '2026-01-15', '5406121446'),
|
||||||
|
('b0000000-0000-0000-0000-000000000002', '22222222-2222-2222-2222-222222222222', 'T12', 'ИИС79/2023', '2025-12-01', '7709258228'),
|
||||||
|
('b0000000-0000-0000-0000-000000000003', '55555555-5555-5555-5555-555555555555', 'T03', 'ИИС80/2024', '2026-02-10', '7728168971')
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- Депо-счета
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO depo_accounts (id, client_id, deponent_code, account_id, section_id, depository_inn, is_active, is_trading) VALUES
|
||||||
|
('c0000000-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111', 'DP789456', '31MC0021900000F01', 'P001', '7702070139', true, true),
|
||||||
|
('c0000000-0000-0000-0000-000000000002', '11111111-1111-1111-1111-111111111111', 'AA789451', '33MC0021900000F02', 'F002', '7802031669', true, true),
|
||||||
|
('c0000000-0000-0000-0000-000000000003', '22222222-2222-2222-2222-222222222222', 'DP100200', '31MC0010000000A01', 'A001', '7702070139', true, true),
|
||||||
|
('c0000000-0000-0000-0000-000000000004', '33333333-3333-3333-3333-333333333333', 'DP300400', '31MC0030000000B01', 'B001', '0702345678', true, true),
|
||||||
|
('c0000000-0000-0000-0000-000000000005', '55555555-5555-5555-5555-555555555555', 'DP500600', '31MC0050000000C01', 'C001', '0710987654', true, true)
|
||||||
|
ON CONFLICT (deponent_code, account_id, section_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- Портфели (остатки ЦБ)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO portfolios (id, client_id, depo_account_id, security_code, isin, quantity_whole, quantity_fractional, valued_at) VALUES
|
||||||
|
('d0000000-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111', 'c0000000-0000-0000-0000-000000000001', 'MM0766162534', 'RU0007661625', 1500, NULL, now()),
|
||||||
|
('d0000000-0000-0000-0000-000000000002', '11111111-1111-1111-1111-111111111111', 'c0000000-0000-0000-0000-000000000001', 'MM0907654321', 'RU0009029540', 300, NULL, now()),
|
||||||
|
('d0000000-0000-0000-0000-000000000003', '11111111-1111-1111-1111-111111111111', 'c0000000-0000-0000-0000-000000000001', 'MM2300100100', NULL, NULL, 2500.75, now()),
|
||||||
|
('d0000000-0000-0000-0000-000000000004', '22222222-2222-2222-2222-222222222222', 'c0000000-0000-0000-0000-000000000003', 'MM0766162534', 'RU0007661625', 5000, NULL, now()),
|
||||||
|
('d0000000-0000-0000-0000-000000000005', '55555555-5555-5555-5555-555555555555', 'c0000000-0000-0000-0000-000000000005', 'MM2300100100', NULL, NULL, 100.00, now())
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -12,7 +12,7 @@ PR-1 → PR-N. Каждая задача — самостоятельный ос
|
|||||||
| PR | Файл | Статус | Зависит от |
|
| PR | Файл | Статус | Зависит от |
|
||||||
|----|------|--------|-----------|
|
|----|------|--------|-----------|
|
||||||
| PR-1 | `PR-1-go-models-m2m.md` | выполнено | — |
|
| PR-1 | `PR-1-go-models-m2m.md` | выполнено | — |
|
||||||
| PR-2 | `PR-2-fansy-ddl.md` | готово к запуску | — (параллельно с PR-1) |
|
| PR-2 | `PR-2-fansy-ddl.md` | выполнено | — (параллельно с PR-1) |
|
||||||
| PR-3 | `PR-3-lk-openapi.md` | готово к запуску | — (параллельно с PR-1) |
|
| PR-3 | `PR-3-lk-openapi.md` | готово к запуску | — (параллельно с PR-1) |
|
||||||
| PR-4 | `PR-4-m2m-core-skeleton.md` | готово к запуску | PR-1 |
|
| PR-4 | `PR-4-m2m-core-skeleton.md` | готово к запуску | PR-1 |
|
||||||
| PR-5 | `PR-5-nsd-adapter-skeleton.md` | ждёт ИШ НРД и сертификаты | PR-1, PR-4 |
|
| PR-5 | `PR-5-nsd-adapter-skeleton.md` | ждёт ИШ НРД и сертификаты | PR-1, PR-4 |
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
-- 000__roles.sql
|
||||||
|
-- Роли для принимающей БД fansy-store.
|
||||||
|
-- Запускать первым, отдельно от структурных миграций.
|
||||||
|
-- Пароли проставляются администратором БД через ALTER ROLE.
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fansy_etl') THEN
|
||||||
|
CREATE ROLE fansy_etl LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE fansy_etl IS
|
||||||
|
'Роль команды Fansy для ETL: INSERT/UPDATE/SELECT в схему fansy_staging. DDL-прав нет.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bj_reader') THEN
|
||||||
|
CREATE ROLE bj_reader LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE bj_reader IS
|
||||||
|
'Роль сервисов Bridge-and-Join-s (m2m-core, lk-gateway) для чтения схемы fansy.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'bj_migrator') THEN
|
||||||
|
CREATE ROLE bj_migrator LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
|
||||||
|
COMMENT ON ROLE bj_migrator IS
|
||||||
|
'Роль с DDL-правами для миграций. Только эта роль может CREATE/ALTER/DROP.';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- 001__schemas.sql
|
||||||
|
-- Две схемы: fansy_staging (куда пишет ETL Fansy) и fansy (рабочая,
|
||||||
|
-- куда переливаются данные после валидации).
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS fansy_staging AUTHORIZATION bj_migrator;
|
||||||
|
COMMENT ON SCHEMA fansy_staging IS
|
||||||
|
'Staging-схема. ETL Fansy делает UPSERT в эти таблицы. Сюда же пишутся ошибки выгрузки.';
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS fansy AUTHORIZATION bj_migrator;
|
||||||
|
COMMENT ON SCHEMA fansy IS
|
||||||
|
'Рабочая схема. Сюда переливаются актуальные данные триггерами или процедурами после валидации staging.';
|
||||||
|
|
||||||
|
-- Права по ролям. DDL-права остаются только у владельца bj_migrator.
|
||||||
|
GRANT USAGE ON SCHEMA fansy_staging TO fansy_etl;
|
||||||
|
GRANT USAGE ON SCHEMA fansy TO bj_reader;
|
||||||
|
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy_staging
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON TABLES TO fansy_etl;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy_staging
|
||||||
|
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO fansy_etl;
|
||||||
|
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA fansy
|
||||||
|
GRANT SELECT ON TABLES TO bj_reader;
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
-- 002__working.sql
|
||||||
|
-- Рабочая схема fansy. Данные сюда переливаются из fansy_staging после
|
||||||
|
-- валидации. Сервисы Bridge-and-Join-s читают только эту схему.
|
||||||
|
|
||||||
|
SET search_path TO fansy, public;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- participants — справочник участников сервиса MOST (контрагенты M2M)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS participants (
|
||||||
|
inn varchar(10) PRIMARY KEY,
|
||||||
|
ogrn varchar(15),
|
||||||
|
full_name_rus text NOT NULL,
|
||||||
|
short_name_rus text,
|
||||||
|
display_name_rus text NOT NULL,
|
||||||
|
full_name_eng text,
|
||||||
|
short_name_eng text,
|
||||||
|
display_name_eng text,
|
||||||
|
depository_participant_code varchar(12),
|
||||||
|
broker_participant_code varchar(12),
|
||||||
|
is_available_for_m2m boolean NOT NULL DEFAULT false,
|
||||||
|
comment text,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn ~ '^[0-9]{10}$'),
|
||||||
|
CHECK (depository_participant_code IS NULL OR depository_participant_code ~ '^[A-Z0-9]+$'),
|
||||||
|
CHECK (broker_participant_code IS NULL OR broker_participant_code ~ '^[A-Z0-9]+$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE participants IS 'Справочник участников сервиса MOST: депозитарии и брокеры, между которыми идут M2M-переводы.';
|
||||||
|
COMMENT ON COLUMN participants.inn IS 'ИНН юрлица (10 цифр), первичный ключ.';
|
||||||
|
COMMENT ON COLUMN participants.depository_participant_code IS 'Код участника M2M на стороне депозитария (для DepositoryPlace в M2MTransferHandbook).';
|
||||||
|
COMMENT ON COLUMN participants.broker_participant_code IS 'Код участника M2M на стороне брокера (для BrokerPlace).';
|
||||||
|
COMMENT ON COLUMN participants.is_available_for_m2m IS 'Готовность участника принимать/отправлять M2M-сообщения (включается после подписания НРД-договора).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_participants_dep_code ON participants(depository_participant_code) WHERE depository_participant_code IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_participants_brk_code ON participants(broker_participant_code) WHERE broker_participant_code IS NOT NULL;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- securities — справочник ценных бумаг
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS securities (
|
||||||
|
security_code char(12) PRIMARY KEY,
|
||||||
|
isin char(12),
|
||||||
|
classification varchar(4),
|
||||||
|
category varchar(4),
|
||||||
|
security_type varchar(256),
|
||||||
|
security_series text,
|
||||||
|
reg_number varchar(256),
|
||||||
|
fund_class varchar(120),
|
||||||
|
display_name text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (security_code ~ '^[0-9A-Z_/-]+$'),
|
||||||
|
CHECK (isin IS NULL OR isin ~ '^[A-Z]{2}[A-Z0-9]{9}[0-9]$'),
|
||||||
|
CHECK (classification IS NULL OR classification IN ('BOND', 'SHAR', 'MFUN')),
|
||||||
|
CHECK (category IS NULL OR category IN ('ORDN', 'PREF', 'UKWN'))
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE securities IS 'Справочник ценных бумаг с их идентификаторами и классификацией.';
|
||||||
|
COMMENT ON COLUMN securities.security_code IS 'Идентификатор ценной бумаги в системе НРД (XSD SecurityCodeType).';
|
||||||
|
COMMENT ON COLUMN securities.classification IS 'Тип ценной бумаги: BOND (облигация), SHAR (акция), MFUN (ПИФ).';
|
||||||
|
COMMENT ON COLUMN securities.category IS 'Категория акций: ORDN (обыкновенные), PREF (привилегированные), UKWN (неизвестно).';
|
||||||
|
COMMENT ON COLUMN securities.reg_number IS 'Регистрационный номер выпуска (для акций и облигаций) или регномер правил доверительного управления ПИФ.';
|
||||||
|
COMMENT ON COLUMN securities.fund_class IS 'Класс паёв ПИФа (если применимо).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_securities_isin ON securities(isin) WHERE isin IS NOT NULL;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- clients — депоненты / инвесторы
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
inn varchar(12),
|
||||||
|
last_name varchar(50) NOT NULL,
|
||||||
|
first_name varchar(50) NOT NULL,
|
||||||
|
middle_name varchar(50),
|
||||||
|
birth_date date,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn IS NULL OR inn ~ '^[0-9]{10,12}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE clients IS 'Депоненты-физлица. Привязка к документам и счетам — через FK из дочерних таблиц.';
|
||||||
|
COMMENT ON COLUMN clients.inn IS 'ИНН физлица (12 цифр) или организации (10 цифр), опционально.';
|
||||||
|
COMMENT ON COLUMN clients.last_name IS 'Фамилия (XSD String50, обязательно).';
|
||||||
|
COMMENT ON COLUMN clients.first_name IS 'Имя (XSD String50, обязательно).';
|
||||||
|
COMMENT ON COLUMN clients.middle_name IS 'Отчество (XSD String50, опционально).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clients_inn ON clients(inn) WHERE inn IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clients_lastname ON clients(last_name, first_name);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- client_documents — документы инвестора
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_documents (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
document_type varchar(2) NOT NULL,
|
||||||
|
series text,
|
||||||
|
number text NOT NULL,
|
||||||
|
issued_at date,
|
||||||
|
issuer text,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (document_type IN (
|
||||||
|
'01','02','03','04','05','06','07','09','10','11','12','13','14',
|
||||||
|
'21','22','23','26','27','91'
|
||||||
|
)),
|
||||||
|
CHECK (series IS NULL OR series ~ '^\S+$'),
|
||||||
|
CHECK (number ~ '^\S+$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE client_documents IS 'Документы, удостоверяющие личность инвестора. Коды по справочнику НРД (XSD IdentityDocumentCodeEnum).';
|
||||||
|
COMMENT ON COLUMN client_documents.document_type IS 'Код вида документа (01..91, см. XSD НРД).';
|
||||||
|
COMMENT ON COLUMN client_documents.series IS 'Серия документа (без пробелов).';
|
||||||
|
COMMENT ON COLUMN client_documents.number IS 'Номер документа (без пробелов, обязательно).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_client_documents_client ON client_documents(client_id);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- iia_contracts — договоры ИИС
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS iia_contracts (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
agreement_type varchar(3) NOT NULL,
|
||||||
|
agreement_number varchar(128) NOT NULL,
|
||||||
|
agreement_date date NOT NULL,
|
||||||
|
broker_inn varchar(10) NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (agreement_type IN ('T12', 'T03')),
|
||||||
|
CHECK (broker_inn ~ '^[0-9]{10}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE iia_contracts IS 'Договоры на ведение ИИС инвестора.';
|
||||||
|
COMMENT ON COLUMN iia_contracts.agreement_type IS 'Тип договора: T12 — ИИС-1/ИИС-2 (старый формат); T03 — ИИС-3 (новый).';
|
||||||
|
COMMENT ON COLUMN iia_contracts.broker_inn IS 'ИНН брокера, с которым заключён договор ИИС.';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_iia_contracts_client ON iia_contracts(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_iia_contracts_broker ON iia_contracts(broker_inn);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- settlement_requisites — реквизиты расчётов (депозитарии)
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS settlement_requisites (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
inn varchar(10) NOT NULL UNIQUE,
|
||||||
|
display_name text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (inn ~ '^[0-9]{10}$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE settlement_requisites IS 'Реквизиты передающего и принимающего депозитариев (XSD SettlementRequisitesType — содержит только ИНН).';
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- depo_accounts — депо-счета и разделы
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS depo_accounts (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
|
||||||
|
deponent_code varchar(50) NOT NULL,
|
||||||
|
account_id varchar(50) NOT NULL,
|
||||||
|
section_id varchar(50) NOT NULL,
|
||||||
|
depository_inn varchar(10) NOT NULL,
|
||||||
|
is_active boolean NOT NULL DEFAULT true,
|
||||||
|
is_trading boolean NOT NULL DEFAULT false,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (depository_inn ~ '^[0-9]{10}$'),
|
||||||
|
UNIQUE (deponent_code, account_id, section_id)
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE depo_accounts IS 'Счета депо инвестора и их разделы у различных депозитариев.';
|
||||||
|
COMMENT ON COLUMN depo_accounts.deponent_code IS 'Код депонента у конкретного депозитария (XSD SettlementDepositoryLocationType.DeponentCode).';
|
||||||
|
COMMENT ON COLUMN depo_accounts.account_id IS 'Номер счёта депо (XSD AccountIdType).';
|
||||||
|
COMMENT ON COLUMN depo_accounts.section_id IS 'Номер раздела счёта депо.';
|
||||||
|
COMMENT ON COLUMN depo_accounts.is_trading IS 'Признак торгового раздела (для отделения от изолированных).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_depo_accounts_client ON depo_accounts(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_depo_accounts_deponent ON depo_accounts(deponent_code);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- portfolios — портфели и остатки ЦБ
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portfolios (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
depo_account_id uuid NOT NULL REFERENCES depo_accounts(id) ON DELETE CASCADE,
|
||||||
|
security_code char(12) NOT NULL REFERENCES securities(security_code),
|
||||||
|
isin char(12),
|
||||||
|
quantity_whole numeric(38, 0),
|
||||||
|
quantity_fractional numeric(38, 16),
|
||||||
|
isolation_status varchar(4) NOT NULL DEFAULT 'SGDN',
|
||||||
|
valued_at timestamptz NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
CHECK (isolation_status IN ('SGDN')),
|
||||||
|
CHECK ((quantity_whole IS NOT NULL) OR (quantity_fractional IS NOT NULL)),
|
||||||
|
CHECK (isin IS NULL OR isin ~ '^[A-Z]{2}[A-Z0-9]{9}[0-9]$')
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE portfolios IS 'Остатки ценных бумаг на счетах депо. Whole/Fractional — choice по XSD QuantityType (заполняется ровно одно).';
|
||||||
|
COMMENT ON COLUMN portfolios.quantity_whole IS 'Целое количество (для акций, облигаций).';
|
||||||
|
COMMENT ON COLUMN portfolios.quantity_fractional IS 'Дробное количество (для паёв ПИФ, до 16 знаков после точки).';
|
||||||
|
COMMENT ON COLUMN portfolios.isolation_status IS 'Статус обособления по XSD НРД, всегда SGDN.';
|
||||||
|
COMMENT ON COLUMN portfolios.valued_at IS 'Дата/время оценки (на какой момент актуален остаток).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_client ON portfolios(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_depo ON portfolios(depo_account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_security ON portfolios(security_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_portfolios_valued_at ON portfolios(valued_at DESC);
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- etl_errors — ошибки выгрузки Fansy
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS etl_errors (
|
||||||
|
id bigserial PRIMARY KEY,
|
||||||
|
source_table text NOT NULL,
|
||||||
|
source_pk text,
|
||||||
|
payload jsonb,
|
||||||
|
error_message text NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE etl_errors IS 'Журнал ошибок выгрузки Fansy: что не смогли записать в staging и почему.';
|
||||||
|
COMMENT ON COLUMN etl_errors.source_table IS 'Название таблицы в источнике (Fansy).';
|
||||||
|
COMMENT ON COLUMN etl_errors.source_pk IS 'Первичный ключ записи в источнике (для повторной попытки).';
|
||||||
|
COMMENT ON COLUMN etl_errors.payload IS 'Сама запись, которую не удалось загрузить (для диагностики).';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_etl_errors_created ON etl_errors(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_etl_errors_table ON etl_errors(source_table);
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
-- 003__staging.sql
|
||||||
|
-- Staging-схема. Структура повторяет fansy.*, плюс loaded_at и
|
||||||
|
-- допущения на промежуточные NULL'ы (валидация будет в процессе
|
||||||
|
-- перелива в fansy.*).
|
||||||
|
|
||||||
|
SET search_path TO fansy_staging, public;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS participants (
|
||||||
|
inn varchar(10) PRIMARY KEY,
|
||||||
|
ogrn varchar(15),
|
||||||
|
full_name_rus text,
|
||||||
|
short_name_rus text,
|
||||||
|
display_name_rus text,
|
||||||
|
full_name_eng text,
|
||||||
|
short_name_eng text,
|
||||||
|
display_name_eng text,
|
||||||
|
depository_participant_code varchar(12),
|
||||||
|
broker_participant_code varchar(12),
|
||||||
|
is_available_for_m2m boolean,
|
||||||
|
comment text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE participants IS 'Staging для справочника участников. Перезаливка целиком, не чаще раза в сутки.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS securities (
|
||||||
|
security_code char(12) PRIMARY KEY,
|
||||||
|
isin char(12),
|
||||||
|
classification varchar(4),
|
||||||
|
category varchar(4),
|
||||||
|
security_type varchar(256),
|
||||||
|
security_series text,
|
||||||
|
reg_number varchar(256),
|
||||||
|
fund_class varchar(120),
|
||||||
|
display_name text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE securities IS 'Staging для справочника ЦБ. Перезаливка целиком.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
inn varchar(12),
|
||||||
|
last_name varchar(50),
|
||||||
|
first_name varchar(50),
|
||||||
|
middle_name varchar(50),
|
||||||
|
birth_date date,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE clients IS 'Staging для клиентов. Инкрементный UPSERT по id.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_documents (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
document_type varchar(2),
|
||||||
|
series text,
|
||||||
|
number text,
|
||||||
|
issued_at date,
|
||||||
|
issuer text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE client_documents IS 'Staging для документов клиента. UPSERT по id.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS iia_contracts (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
agreement_type varchar(3),
|
||||||
|
agreement_number varchar(128),
|
||||||
|
agreement_date date,
|
||||||
|
broker_inn varchar(10),
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE iia_contracts IS 'Staging для договоров ИИС.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS settlement_requisites (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
inn varchar(10) NOT NULL,
|
||||||
|
display_name text,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE settlement_requisites IS 'Staging для реквизитов расчётов.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS depo_accounts (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
deponent_code varchar(50),
|
||||||
|
account_id varchar(50),
|
||||||
|
section_id varchar(50),
|
||||||
|
depository_inn varchar(10),
|
||||||
|
is_active boolean,
|
||||||
|
is_trading boolean,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE depo_accounts IS 'Staging для депо-счетов.';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS portfolios (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
client_id uuid NOT NULL,
|
||||||
|
depo_account_id uuid NOT NULL,
|
||||||
|
security_code char(12) NOT NULL,
|
||||||
|
isin char(12),
|
||||||
|
quantity_whole numeric(38, 0),
|
||||||
|
quantity_fractional numeric(38, 16),
|
||||||
|
isolation_status varchar(4),
|
||||||
|
valued_at timestamptz,
|
||||||
|
loaded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE portfolios IS 'Staging для портфелей. UPSERT по id; SLA свежести — 1 мин.';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stg_portfolios_loaded ON portfolios(loaded_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stg_clients_loaded ON clients(loaded_at DESC);
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
-- 004__seed_participants.sql
|
||||||
|
-- Предзаполнение справочника участников по DOC/Справочник пользователей.pdf
|
||||||
|
-- НРД и тестовые контрагенты Регламента M2M.
|
||||||
|
|
||||||
|
SET search_path TO fansy, public;
|
||||||
|
|
||||||
|
INSERT INTO participants (
|
||||||
|
inn, ogrn, full_name_rus, short_name_rus, display_name_rus,
|
||||||
|
full_name_eng, short_name_eng, display_name_eng,
|
||||||
|
depository_participant_code, broker_participant_code,
|
||||||
|
is_available_for_m2m, comment
|
||||||
|
) VALUES
|
||||||
|
(
|
||||||
|
'7702165310', '1027739132563',
|
||||||
|
'Небанковская кредитная организация акционерное общество "Национальный расчётный депозитарий"',
|
||||||
|
'НКО АО НРД', 'НРД',
|
||||||
|
'National Settlement Depository', 'NSD', 'NSD',
|
||||||
|
'MC0010300000', NULL, true,
|
||||||
|
'Центральный депозитарий, держатель реестра M2M-сделок.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'5406121446', '1025402459334',
|
||||||
|
'Общество с ограниченной ответственностью "Компания БКС"',
|
||||||
|
'ООО "Компания БКС"', 'БКС',
|
||||||
|
'BCS Company Ltd', 'BCS', 'BCS',
|
||||||
|
NULL, 'MC0079200001', true,
|
||||||
|
'Брокер БКС, контрагент M2M.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'7709258228', '1027739675260',
|
||||||
|
'Общество с ограниченной ответственностью "Ренессанс Брокер"',
|
||||||
|
'ООО "Ренессанс Брокер"', 'Ренессанс Брокер',
|
||||||
|
'Renaissance Broker Ltd', 'Renaissance', 'Renaissance',
|
||||||
|
NULL, 'MC0010300032', true,
|
||||||
|
'Брокер Ренессанс, контрагент M2M.'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'7728168971', '1027700067328',
|
||||||
|
'Акционерное общество "Альфа-Банк"',
|
||||||
|
'АО "Альфа-Банк"', 'Альфа-Банк',
|
||||||
|
'Alfa-Bank JSC', 'Alfa-Bank', 'Alfa-Bank',
|
||||||
|
NULL, 'MC0079200033', true,
|
||||||
|
'Брокер Альфа-Банк, контрагент M2M.'
|
||||||
|
)
|
||||||
|
ON CONFLICT (inn) DO UPDATE SET
|
||||||
|
full_name_rus = EXCLUDED.full_name_rus,
|
||||||
|
short_name_rus = EXCLUDED.short_name_rus,
|
||||||
|
display_name_rus = EXCLUDED.display_name_rus,
|
||||||
|
full_name_eng = EXCLUDED.full_name_eng,
|
||||||
|
short_name_eng = EXCLUDED.short_name_eng,
|
||||||
|
display_name_eng = EXCLUDED.display_name_eng,
|
||||||
|
depository_participant_code = EXCLUDED.depository_participant_code,
|
||||||
|
broker_participant_code = EXCLUDED.broker_participant_code,
|
||||||
|
is_available_for_m2m = EXCLUDED.is_available_for_m2m,
|
||||||
|
comment = EXCLUDED.comment,
|
||||||
|
updated_at = now();
|
||||||
Reference in New Issue
Block a user