93bcbca12c
- 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>
232 lines
14 KiB
SQL
232 lines
14 KiB
SQL
-- 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);
|