{{define "content"}}

bj ↔ bj2depo: трансферные таблицы FTV_M2M и события

Для разработчика Fancy (сервис bj2depo). Как bj заполняет FTV_M2M / FTV_M2M_LINES, какими идентификаторами располагает, какие события пишет в FTV_M2M_EVENTS и какие ожидает в ответ. Согласовано 10.09.2026 и реализовано в bj: события BJ_* пишутся, читаются DEPO_ORDER_CREATED и DEPO_ERROR, справочник участников перезаписывается при каждом получении.

Кто что делает

СервисРоль
bj (Bridge-and-Join-s)обмен с НРД по MOEX МОСТ (M2MTR / M2MTD / M2MER, депо-поручения 16 и 16/1, отчёты MS010), сверка с DEPO/SPECTRE, блокировка и движение бумаг в QUIK МНП, личный кабинет. Пишет поручения и события в трансферные таблицы, читает события от DEPO.
bj2depo (джоба Fancy)читает FTV_M2M / FTV_M2M_LINES → создаёт и меняет сущности DEPO (M2M-поручения D_CLISER_M2M, строки D_CLISER_M2M_DOL); обрабатывает события FTV_M2M_EVENTS от bj; пишет свои события для bj.

Работаем только с НРД (MOEX МОСТ), СПБ нет. Переводятся только выпуски с ISIN, начинающимся на RU. База — наша копия DEPO на SQL002 (BJ_DEPO_DATA); джобу bj2depo нужно поднять на ней (сейчас она работает на SQL001/UPD_DE_DATA, и записи в нашей базе никто не забирает).

Какими идентификаторами располагает bj

СущностьЧто есть у bjЧто можем передавать в таблицы
ДепонентФИО + серия/номер паспорта из сообщения НРД. bj сам находит лицо в DEPO по паспорту (серия без пробелов, OD_FACES.DOC_SER/DOC_NUM).DEPONENT_NAME, DEPONENT_DOC и DEPONENT_ID = OD_FACES.SELF_ID (мастер-ID лица; у дублей указывает на основную запись).
Контрагент (депозитарий)код участника МОСТ (MC…, 12 знаков), депозитарный код НРД (HL…), ИНН, наименование из справочника участников M2M. В OD_FACES депозитариев-контрагентов нет (проверено по ИНН), есть только сам НКО АО НРД (ID 18174).CONTRAGENT_M2M_CODE; при желании ИНН и наименование — доп. поля.
БумагаISIN + код НРД (напр. SBER/03); bj находит OD_VALUES.ID по ISIN (IS_IN=1 = принята на обслуживание).ISIN, SEC_CODE и SHARE = OD_VALUES.ID.
Счёт клиентаbj находит открытый торговый счёт ВТНКЦ… (OD_CLIENTS, STATE=0). При нескольких договорах берётся самый ранний (правило депозитария).OD_CLIENTS.ID и NUMBER.
Счёт депозитария (место хранения)наш счёт в НРД HL171004001C + раздел 36MC04136…. В DEPO это запись OD_CLIENTS с NUMBER HL171004001C/253400M18U5TB02TW421/253400_X1A47/<раздел> (например ID 79 для раздела 36MC0413600000F00). OD_CLIENTS_REC.LINK_NUMBER у нас пустой везде.PLACE_NUMBER = раздел (или ID записи OD_CLIENTS, если удобнее).
Причины отказакоды M2M01…M2M10 по каждой бумаге (может быть несколько), при отказе НРД по поручению 16/16-1 — код и текст из MS010.M2M_REJECT_CODE через ;, текст — в COMMENT.
КлючиGUID перевода (из M2MTR) и REFERENCE строки (референс бумаги из M2MTR, M2M…).как ключи FTV_M2M / FTV_M2M_LINES / FTV_M2M_EVENTS.

Таблицы (как согласовано, поля, которые заполняет bj)

ТаблицаПоля от bj
FTV_M2MGUID, DIRECTION (IN — к нам зачисляют, OUT — мы списываем), CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, DEPONENT_ID (если нашли), COMMENT. Вставка вместе со строками в одной транзакции.
FTV_M2M_LINESGUID, REFERENCE, ISIN, SEC_CODE, SHARE (если нашли), AMOUNT (штуки, дробные для паёв), PLACE_NUMBER, DEPO_NUMBER — номер торгового счёта клиента ВТНКЦ… (джоба находит по нему DEPO).
FTV_M2M_EVENTSGUID, REFERENCE (пусто = по поручению целиком), EVENT_TYPE, EVENT_DATE, M2M_REJECT_CODE, COMMENT, STATE (см. правила).

Поручение создаётся в DEPO на самом раннем этапе: сразу после записи в FTV_M2M (для входящих — как только пришёл M2MTR; для исходящих — как только наш M2MTR ушёл в НРД).

Правила для событий

События от bj (обрабатывает bj2depo)

EVENT_TYPEКогда bj пишетУровеньЧто ожидаем от bj2depo
BJ_RECEIVEDпришёл входящий M2MTR от другого депозитария (мы зачисляем); одновременно вставлены FTV_M2M + LINESпоручениесоздать M2M-поручение в DEPO, вернуть DEPO_ORDER_CREATED
BJ_SENTнаш исходящий M2MTR ушёл в НРД (мы списываем)поручението же для списания
BJ_DECISION_CONFIRM / BJ_DECISION_REJECTмы ответили контрагенту по бумаге (M2MTD); при отказе — кодыстрокапроставить статус строки: принято / отказано с кодами
BJ_CP_CONFIRM / BJ_CP_REJECTконтрагент ответил по нашему исходящему (M2MTD)строкато же для списания
BJ_SERVICE_ERRORсервис МОСТ отверг перевод (M2MER ERROR), код M2Mxxпоручениезакрыть поручение как отклонённое
BJ_ORDER_SENTподано депо-поручение 16 (списание) или 16/1 (зачисление) в НРД; в COMMENT — CONTR_D_ID и датапоручениепривязать номер поручения НРД
BJ_ORDER_ACCEPTED / BJ_ORDER_REJECTEDНРД принял (отчёт MS010) или отверг наше депо-поручение; при отказе — код и текстпоручениепроставить результат; при принятии — провести движение по счёту, если DEPO делает это по нашему сигналу (см. вопросы)
BJ_ORDER_LATEMS010 не получен за 5 минут после подачи (регламент)поручениедля информации / контроля
BJ_EXECUTEDперевод исполнен (по отчёту НРД)поручениезакрыть как исполненное
BJ_CANCELLEDперевод отозван / аннулирован / истёк срок 30 дней без исполненияпоручениезакрыть как отменённое
BJ_GATEWAY_REJECTшлюз bj отказал в исходящем переводе из ЛК до отправки в НРД (сверка: лицо, счёт, бумага, остаток, место хранения, блокировки; либо данные заявки некорректны). Код в M2M_REJECT_CODE (M2Mxx или QTY/PLACE/QUIK), причина в COMMENT; одновременно вставлены FTV_M2M (OUT) + LINESстроказакрыть строку и документ как отклонённые (см. «Действия джобы»); пометить событие SUCCESS или ERROR — только после этого bj ставит заявке в ЛК «Отклонена»
BJ_PINGпроверка готовности обработчика из настроек bj (GUID случайный, без шапки в FTV_M2M)служебноеничего не делать, только пометить SUCCESS — bj ждёт отметку до 40 с и удаляет строку
BJ_QUIK_BLOCKED / BJ_QUIK_RELEASED / BJ_QUIK_SIGNED / BJ_QUIK_DEPOSITEDблокировка / снятие / списание / зачисление в QUIK МНП; номер поручения МНП в COMMENTстрокатолько для сверки QUIK ↔ DEPO, в DEPO ничего не менять

События от bj2depo (обрабатывает bj)

Согласовано: bj2depo пишет только DEPO_ORDER_CREATED и DEPO_ERROR. Остальные типы из таблицы ниже (проверка, исполнение, отмена) остаются в резерве на случай, если понадобятся позже; bj их пока не ждёт.

EVENT_TYPEКогда bj2depo пишетУровеньЧто делает bj
DEPO_ORDER_CREATEDпоручение в DEPO создано; номер документа в COMMENT (и/или FANSY_DOC в FTV_M2M)поручениепоказывает номер в карточке перевода, далее ссылается на него
DEPO_ERRORджоба не смогла обработать событие или поручение (текст)любойоповещение оператору bj

Согласовано с программистом Fancy: от bj2depo достаточно двух событий, DEPO_ORDER_CREATED и DEPO_ERROR. Проверку строк по регламенту делает bj (сверка с DEPO), состояние документа bj читает сам (раздел «Обратная связь в bj»), поэтому отдельные события о проверке, исполнении и отмене не нужны.

Порядок событий по этапам

Входящий перевод (мы зачисляем)Исходящий перевод (мы списываем)
1. bj: FTV_M2M + LINES, BJ_RECEIVED1. bj: QUIK блокирует бумаги (BJ_QUIK_BLOCKED), M2MTR в НРД, FTV_M2M + LINES, BJ_SENT
2. bj2depo: DEPO_ORDER_CREATED; bj сверяет строки с DEPO2. bj2depo: DEPO_ORDER_CREATED
3. bj: ответ контрагенту — BJ_DECISION_CONFIRM / BJ_DECISION_REJECT3. bj: ответ контрагента — BJ_CP_CONFIRM / BJ_CP_REJECT (при отказе QUIK снимает блокировку: BJ_QUIK_RELEASED)
4. bj: подача 16/1 — BJ_ORDER_SENT4. bj: подача 16 — BJ_ORDER_SENT
5. bj: BJ_ORDER_ACCEPTED (MS010) или BJ_ORDER_REJECTED; QUIK зачисляет — BJ_QUIK_DEPOSITED5. bj: BJ_ORDER_ACCEPTED / BJ_ORDER_REJECTED; QUIK списывает — BJ_QUIK_SIGNED
6. bj2depo: движение по счёту депо по BJ_ORDER_ACCEPTED / BJ_EXECUTED, документ в «Конец обработки»6. bj2depo: то же, списание
Отклонения: BJ_GATEWAY_REJECT (шлюз bj отказал до отправки в НРД; статус в ЛК — после обработки джобой), BJ_SERVICE_ERROR, BJ_ORDER_LATE (5 мин без MS010), BJ_CANCELLED (30 дней без исполнения), DEPO_ERROR.

Действия джобы по событиям: что делать с документом DEPO

Сейчас джоба создаёт документ по шапке и помечает события обработанными, но документ и его строки не меняются: после отказа документ остаётся в состоянии «Начальный ввод» и его приходится отменять руками, а после приёма остаток на счёте появляется, но документ в конечное состояние не переходит (проверено 14.09.2026 на документах 75070271 и 75070273). Ниже — что джобе нужно делать по каждому событию. Документ всегда находится по GUID, строка — по GUID + REFERENCE; событие без REFERENCE относится ко всему документу.

СобытиеУровеньЧто сделать в DEPOКонечное состояние
BJ_RECEIVED, BJ_SENTдокументдокумент уже создан по шапке; ничего не делать, пометить SUCCESS«Начальный ввод» (0) → можно «Начало обработки» (1)
BJ_DECISION_REJECT (входящий: мы отказали), BJ_CP_REJECT (исходящий: отказал контрагент)строкав D_CLISER_M2M_DOL строки записать код из M2M_REJECT_CODE в M2M_STATUS/M2M_TEXT (текст из COMMENT). Если отказаны все строки документа — выполнить шаг «Отмена» (тип шага 0→REJ), как это делает оператор вручнуюстрока «отказ, M2Mxx»; документ «Отмена обработки» (3)
BJ_DECISION_CONFIRM, BJ_CP_CONFIRMстрокаотметить строку как принятую (M2M_STATUS «принято»); движения по счёту пока нет — ждём поручение НРДстрока «принято»; документ «Начало обработки» (1)
BJ_ORDER_SENTдокументзаписать номер депо-поручения НРД (CONTR_D_ID из COMMENT); состояние — ожидание«Ожидание доставки (ответа)» (10) или «Ожидание исполнения операции» (127)
BJ_ORDER_ACCEPTED + BJ_EXECUTEDдокументпровести движение по счёту депо по всем принятым строкам: входящий — зачисление на торговый счёт клиента (то, что оператор сделал вручную 14.09 для 75070271: появился остаток 10 SBER на 836), исходящий — списание; затем закрыть документ«Конец обработки (в архиве)» (2)
BJ_GATEWAY_REJECTстрокав D_CLISER_M2M_DOL строки записать код из M2M_REJECT_CODE и причину из COMMENT; если отказаны все строки — шаг «Отмена». Документ мог не создаться (например, бумаги с таким ISIN нет в OD_VALUES) — тогда достаточно пометить событие ERROR с текстом причиныстрока «отказ шлюза»; документ «Отмена обработки» (3)
BJ_ORDER_REJECTED, BJ_SERVICE_ERROR, BJ_CANCELLEDдокументзаписать причину (COMMENT / код) в документ, выполнить шаг «Отмена»«Отмена обработки» (3)
BJ_ORDER_LATEдокументтолько пометка/комментарий («отчёт НРД не получен за 5 минут»); состояние не менятьбез изменений
BJ_QUIK_*, BJ_PINGслужебныеничего не делать, пометить SUCCESSбез изменений

Названия и коды состояний — из справочника OD_DOC_STATES (D_CAT=0): 0 «Начальный ввод», 1 «Начало обработки (зарегистрирован)», 2 «Конец обработки (в архиве)», 3 «Отмена обработки», 10 «Ожидание доставки (ответа)», 127 «Ожидание исполнения операции». Шаг отмены, который выполнял оператор: OD_STEP_TYPES ID 75069472 «Отмена» (SYS_NAME 0->REJ, OUT_STATE 3).

Запрос, которым джоба находит отказы вместе с документом и строкой:

SELECT e.ID, e.GUID, e.REFERENCE, e.M2M_REJECT_CODE, e.COMMENT, m.DOC, l.DOL
FROM dbo.FTV_M2M_EVENTS e
JOIN dbo.FTV_M2M m ON m.GUID = e.GUID
JOIN dbo.FTV_M2M_LINES l ON l.GUID = e.GUID AND l.REFERENCE = e.REFERENCE
WHERE e.STATE = 'TODO' AND e.EVENT_TYPE IN ('BJ_DECISION_REJECT', 'BJ_CP_REJECT')
ORDER BY e.ID;

Для событий по документу целиком (REFERENCE пуст) джойн со строками не нужен:

SELECT e.ID, e.GUID, e.EVENT_TYPE, e.M2M_REJECT_CODE, e.COMMENT, m.DOC
FROM dbo.FTV_M2M_EVENTS e
JOIN dbo.FTV_M2M m ON m.GUID = e.GUID
WHERE e.STATE = 'TODO' AND e.REFERENCE IS NULL
ORDER BY e.ID;

Обратная связь в bj. bj сам читает состояние документа по номеру (OD_DOCS.STATE по FTV_M2M.DOC) и показывает его в карточке перевода; при ручной отмене в DEPO оператор bj получает оповещение. Поэтому отдельные события от джобы о смене состояния не требуются — достаточно, чтобы документ переходил в состояния из таблицы выше.

Справочник участников M2M (M2MTH) → таблица FTV_M2M_HANDBOOK

НРД ведёт справочник участников переводов по MOEX МОСТ: кто депозитарий, кто брокер, их коды участника и ИНН, плюс места расчётов. bj получает его от НРД (сообщение M2MTransferHandbook) при старте и обновляет раз в сутки; по нему bj определяет контрагента по ИНН из перевода и наполняет справочник депозитариев в QUIK МНП. Предлагаем отдавать его в DEPO целиком в отдельную таблицу — тогда CONTRAGENT_M2M_CODE из FTV_M2M раскрывается в ИНН и наименование, и по нему можно заводить контрагентов в DEPO.

Сейчас в справочнике 158 участников и 2 мест расчётов (получено 2026-09-09). Наш код участника — MC0413600000.

Поле таблицыТипОткуда у bj / что означает
INNvarchar(12)ИНН организации. Вместе с PARTICIPANT_CODE и KIND — ключ строки.
PARTICIPANT_CODEvarchar(12)код участника МОСТ (MC…, 12 знаков) — тот самый CONTRAGENT_M2M_CODE. У мест расчётов пусто.
KINDvarchar(8)DEPO — депозитарий, BROKER — брокер (одна организация может быть в обоих качествах — две строки), PLACE — место расчётов.
NAME_FULLnvarchar(255)полное наименование (рус.)
NAME_SHORTnvarchar(128)краткое наименование (рус.)
NAME_DISPLAYnvarchar(255)отображаемое наименование (рус.) — то, что показываем в интерфейсах
NAME_ENGnvarchar(255)наименование на английском (есть не у всех)
HANDBOOK_ATdatetimeдата/время версии справочника НРД (CreationTimestamp)
UPDATED_ATdatetimeкогда bj записал строку
ACTIVEbitвсегда 1 (история не ведётся, таблица перезаписывается целиком)

Обновление (согласовано). История не нужна: при каждом получении справочника от НРД bj удаляет все строки и записывает содержимое заново одной транзакцией (DEPO не видит пустую таблицу; DELETE вместо TRUNCATE, потому что TRUNCATE требует права ALTER) — аналог нового HANDBOOK.xml. Дополнительно перезапись повторяется при старте bj и раз в 6 часов. Просьба создать таблицу по скрипту ниже (создаёт наша сторона: у логина bj нет права CREATE TABLE в BJ_DEPO_DATA). Поля — как в описании; bj2depo использует INN, PARTICIPANT_CODE, NAME_SHORT. Английские наименования и места расчётов bj пишет, джоба их не использует.

Примеры реальных строк:

INNPARTICIPANT_CODEKINDNAME_DISPLAY
7707025725MC0005600000DEPOАО АКБ "ЦентроКредит"
9714012520MC0418000000DEPOИК ФИАНИТ
7831000034—PLACEАО "СПБ Банк"

Как это связано с остальным: в FTV_M2M поле CONTRAGENT_M2M_CODE = PARTICIPANT_CODE строки с KIND=DEPO; ИНН отправителя/получателя в сообщениях НРД (TransferringDepository/ReceivingDepository INN) = INN. Тот же справочник bj загружает в QUIK МНП (таблица M2MDepoRecipients: ИНН + наименование).

Кто запускает обработку: джоба SQL Agent или bj

Обработка трансферных таблиц — это вызов процедуры FTV_M2M_JOB в базе DEPO. Есть два равноценных способа её запускать:

СпособЧто нужно на сервере клиентаПлюсы / минусы
A. Джоба SQL Agent bj2depo (как сейчас у нас: шаг exec FTV_M2M_JOB, каждые 10 с)SQL Agent запущен; права на создание джобы (SQLAgentOperatorRole / sysadmin); учётка Agent с правами на BJ_DEPO_DATAработает независимо от bj; но у каждого клиента нужно заводить джобу и права, а bj не видит её состояние без доступа к msdb
B. bj вызывает процедуру сам (Настройка → Обмен Fancy → «Обработка трансферных таблиц DEPO»)только GRANT EXECUTE ON dbo.FTV_M2M_JOB TO <логин bj>одна настройка на все внедрения, состояние и ошибки видны в bj; обработка идёт, только пока работает bj

Включать нужно один вариант, иначе процедура будет вызываться параллельно из двух мест. Просьба к разработчику: сделать FTV_M2M_JOB безопасной при повторном/параллельном вызове (обработка только строк со STATE='TODO', блокировка строки на время обработки) — тогда случайное двойное включение не навредит.

Результат выгрузки истории тестов (14.09.2026)

Кнопкой «Выгрузить историю в DEPO» на вкладке «Тестирование» bj записал в трансферные таблицы все переводы, проведённые на TEST3 (робот НРД и банк Зенит): 78 переводов, 252 события с реальными датами. Джоба bj2depo создала по каждому документ в DEPO (D_CLISER_M2M + строки), сама определила контрагента, депонента, бумагу и место хранения. Что при этом выяснилось:

Примеры SQL

Так bj заведёт входящий перевод (одна транзакция):

BEGIN TRAN;
INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, DEPONENT_ID, COMMENT)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'IN', 'MC0034900000', N'Погонщикова Виктория Олеговна', '4500 123456', 11773497, N'M2MTR от MC0034900000');
INSERT INTO dbo.FTV_M2M_LINES (GUID, REFERENCE, ISIN, SEC_CODE, SHARE, AMOUNT, PLACE_NUMBER)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'M2M0000002675550', 'RU000A107UL4', 'RU000A107UL4', 18824, 1, '36MC0413600000F00');
INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, STATE, EVENT_TYPE, EVENT_DATE, COMMENT)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', NULL, 'TODO', 'BJ_RECEIVED', GETDATE(), N'входящий перевод получен');
COMMIT;

Так bj2depo сообщает, что документ в DEPO создан:

INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, STATE, EVENT_TYPE, EVENT_DATE, COMMENT)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', NULL, 'TODO', 'DEPO_ORDER_CREATED', GETDATE(), N'75070271');

Так bj забирает свои события и подтверждает обработку:

SELECT ID, GUID, REFERENCE, EVENT_TYPE, M2M_REJECT_CODE, COMMENT
FROM dbo.FTV_M2M_EVENTS WHERE EVENT_TYPE LIKE 'DEPO_%' AND STATE = 'TODO' ORDER BY ID;
UPDATE dbo.FTV_M2M_EVENTS SET STATE = 'SUCCESS', TimeBeg = GETDATE(), TimeEnd = GETDATE(), COMMENT = N'применено в bj' WHERE ID = @id;

Структура таблицы справочника и то, как bj её перезаписывает:

CREATE TABLE dbo.FTV_M2M_HANDBOOK (
  ID int IDENTITY(1,1) PRIMARY KEY, INN varchar(12) NOT NULL, PARTICIPANT_CODE varchar(12) NULL, KIND varchar(8) NOT NULL,
  NAME_FULL nvarchar(255) NULL, NAME_SHORT nvarchar(128) NULL, NAME_DISPLAY nvarchar(255) NULL, NAME_ENG nvarchar(255) NULL,
  HANDBOOK_AT datetime NULL, UPDATED_AT datetime NOT NULL DEFAULT GETDATE(), ACTIVE bit NOT NULL DEFAULT 1);
-- при каждом получении справочника от НРД (одна транзакция):
BEGIN TRAN;
DELETE FROM dbo.FTV_M2M_HANDBOOK;
INSERT INTO dbo.FTV_M2M_HANDBOOK (INN, PARTICIPANT_CODE, KIND, NAME_FULL, NAME_SHORT, NAME_DISPLAY, NAME_ENG, HANDBOOK_AT, UPDATED_AT, ACTIVE)
VALUES ('7707025725', 'MC0005600000', 'DEPO', N'…', N'АО АКБ "ЦентроКредит"', N'…', N'…', @hb_at, GETDATE(), 1); -- и так по всем участникам
COMMIT;

Открытые вопросы

Закрыто 15.09.2026:

Остаётся:

  1. Создать таблицу FTV_M2M_HANDBOOK в BJ_DEPO_DATA по скрипту из раздела «Справочник участников».
  2. Действия джобы по событиям (раздел «Действия джобы»): движение по счёту депо по BJ_ORDER_ACCEPTED / BJ_EXECUTED, запись отказов в строки и шаг «Отмена».
{{end}}