Files
Bridge-and-Join-s/internal/lkgateway/m2mcodes.go
T

174 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
import (
"html"
"strings"
)
// m2mCodeInfo — запись справочника кодов M2M: код, краткое название и развёрнутое
// пояснение простым языком (когда и почему возникает).
type m2mCodeInfo struct {
Code, Short, Full string
}
// m2mRejectCatalog — коды ОТКАЗА зачисляющей (принимающей) стороны. Приходят в
// решении M2MTransferDecision по конкретной бумаге.
var m2mRejectCatalog = []m2mCodeInfo{
{"M2M01", "Ценная бумага не допускается к переводам M2M",
"Бумагу нельзя переводить через сервис M2M. Типичный случай — иностранный выпуск (ISIN не начинается с «RU»): сервис МОСТ такие бумаги между депозитариями не пропускает."},
{"M2M02", "Не может быть определён счёт зачисления",
"Зачисляющий депозитарий не смог определить, на какой счёт депо зачислять бумаги: клиент не указал счёт по умолчанию, а он для операции необходим."},
{"M2M03", "Счёт депо зачисления не торговый",
"Указанный счёт зачисления не является торговым — зачисление бумаг на него невозможно."},
{"M2M04", "Инвестор не найден",
"У зачисляющего депозитария нет такого клиента (владельца бумаг): некому зачислять. Возникает, если инвестор не заведён у принимающей стороны."},
{"M2M05", "Бумага не обслуживается зачисляющим депозитарием",
"Зачисляющий депозитарий не оказывает услуги по учёту прав на данную ценную бумагу (не работает с этим выпуском)."},
{"M2M06", "Приостановление операций по счёту зачисления",
"По счёту депо зачисления действует приостановка/блокировка операций — зачислить бумаги нельзя."},
{"M2M07", "Отказ принимающей стороны",
"Общий отказ зачисляющего депозитария. В нашей системе применяется также для повторного (дублирующего) перевода — тот же инвестор и бумаги уже принимались ранее."},
{"M2M08", "Операция нарушит законодательство РФ",
"Проведение перевода приведёт к нарушению закона: например, бумага предназначена только для квалифицированных инвесторов, а инвестор не квалифицирован."},
{"M2M09", "Отказ принимающей стороны (доп.)",
"Дополнительный код отказа зачисляющей стороны."},
}
// m2mServiceCatalog — коды сервиса МОСТ (ошибки/информирование). Приходят в ответе
// сервиса M2MTransferResponse.
var m2mServiceCatalog = []m2mCodeInfo{
{"M2M10", "Получено невалидное сообщение",
"Формат или структура запроса не прошли проверку сервиса МОСТ (сообщение не соответствует схеме)."},
{"M2M14", "Списывающий не в справочнике участников M2M",
"Отправитель (списывающий депозитарий) не зарегистрирован в справочнике участников сервиса M2M."},
{"M2M15", "Зачисляющий не в справочнике участников M2M",
"Получатель (зачисляющий депозитарий) не зарегистрирован в справочнике участников сервиса M2M."},
{"M2M17", "Неверно указан ИНН списывающего депозитария",
"ИНН передающей (списывающей) стороны указан неверно или не совпадает со справочником."},
{"M2M18", "Неверно указан ИНН зачисляющего депозитария",
"ИНН принимающей (зачисляющей) стороны указан неверно или не совпадает со справочником."},
{"M2M19", "Недопустимое место расчётов",
"Указанные депонент / счёт депо / раздел / расчётный депозитарий не входят в допустимые для перевода M2M."},
{"M2M30", "Неверно указан код НРД ценной бумаги",
"Код НРД ценной бумаги указан неверно (не найден в справочнике инструментов)."},
{"M2M31", "Ответ по этому GUID уже обработан",
"Ответ на запрос на перевод M2M с указанным GUID уже был обработан ранее — повторная (дублирующая) отправка ответа/решения."},
}
// m2mSpravkaHTML — справка по кодам ошибок/отказа M2M простым языком (для страницы
// и для вложения в пакет отчётов контрагенту).
func m2mSpravkaHTML(standalone bool) []byte {
esc := html.EscapeString
var b strings.Builder
if standalone {
b.WriteString(`<!doctype html><html lang="ru"><head><meta charset="utf-8"><title>Справка по кодам M2M</title><style>`)
b.WriteString(`body{font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#1a1a1a;max-width:900px;margin:24px auto;padding:0 20px;line-height:1.5}`)
b.WriteString(`h1{font-size:20px}h2{font-size:15px;margin:22px 0 8px;color:#1f4e79}table{border-collapse:collapse;width:100%;font-size:13px;margin:8px 0}`)
b.WriteString(`td,th{border:1px solid #ccc;padding:7px 10px;text-align:left;vertical-align:top}th{background:#f0f0f0}`)
b.WriteString(`.c{font-family:monospace;font-weight:700;white-space:nowrap}.muted{color:#666}</style></head><body>`)
}
b.WriteString(`<h1>Справка по кодам ошибок и отказов M2M (MOEX МОСТ)</h1>`)
b.WriteString(`<p class="muted">Коды, которыми сервис МОСТ и зачисляющий депозитарий сообщают о причине отказа или ошибки при переводе ценных бумаг между депозитариями.</p>`)
section := func(title, note string, cat []m2mCodeInfo) {
b.WriteString(`<h2>` + title + `</h2>`)
if note != "" {
b.WriteString(`<p class="muted" style="font-size:12.5px">` + note + `</p>`)
}
b.WriteString(`<table><tr><th style="width:90px">Код</th><th style="width:34%">Кратко</th><th>Что это значит</th></tr>`)
for _, ci := range cat {
b.WriteString(`<tr><td class="c">` + esc(ci.Code) + `</td><td>` + esc(ci.Short) + `</td><td>` + esc(ci.Full) + `</td></tr>`)
}
b.WriteString(`</table>`)
}
section("Отказ зачисляющей (принимающей) стороны",
"Приходят в решении M2MTransferDecision по конкретной ценной бумаге — принимающий депозитарий отказал в зачислении.",
m2mRejectCatalog)
section("Ошибки и информирование сервиса МОСТ",
"Приходят в ответе сервиса M2MTransferResponse — сервис отклонил или прокомментировал запрос.",
m2mServiceCatalog)
b.WriteString(`<p class="muted" style="font-size:12px;margin-top:16px">Прочие коды сервиса (M2M11–M2M13, M2M16, M2M20–M2M29, M2M32–M2M43) определяются Спецификацией электронных документов НРД (Приложение 5) и в данной справке не расшифрованы.</p>`)
if standalone {
b.WriteString(`</body></html>`)
}
return []byte(b.String())
}
// m2mCodeTexts — человекочитаемые названия кодов ошибок/отказа M2M НРД.
// m2mCodeTexts — человекочитаемые названия кодов ошибок/отказа M2M НРД.
// Источники: ответы сервиса МОСТ (Text), инструкция по тестированию с роботом
// (Приложение 5, набор данных об ошибках). M2M01–M2M09 — тестовые коды отказа
// робота (имитация отказа принимающей стороны).
var m2mCodeTexts = map[string]string{
"M2M01": "Ценная бумага не допускается к переводам M2M",
"M2M02": "Не может быть определён счёт для зачисления ценных бумаг",
"M2M03": "Счёт депо зачисления не торговый",
"M2M04": "Инвестор не найден",
"M2M05": "Зачисляющий депозитарий не оказывает услуги по учёту прав на ценную бумагу",
"M2M06": "Приостановление операций по счёту депо зачисления",
"M2M07": "Отказ принимающей стороны",
"M2M08": "Проведение операции приведёт к нарушению законодательства РФ",
"M2M09": "Отказ принимающей стороны (дополнительный код)",
"M2M10": "Получено невалидное сообщение",
"M2M14": "Отправитель не зарегистрирован в справочнике участников M2M",
"M2M15": "Получатель (зачисляющий депозитарий) не зарегистрирован в справочнике участников M2M", // предположительно (пара к M2M14); уточнить по Спецификации ЭД
"M2M17": "Неверно указан ИНН списывающего депозитария",
"M2M18": "Неверно указан ИНН зачисляющего депозитария", // предположительно (по соседним кодам); уточнить по Спецификации ЭД
"M2M19": "Недопустимое место расчётов",
"M2M30": "Неверно указан код НРД ценной бумаги",
"M2M43": "Ценная бумага не учитывается в НРД", // текст НРД из ответа сервиса (29.09.2026)
// Внутренние причины отказа сверки списания (не коды НРД): недостаточно бумаг
// на торговом счёте и неверное место хранения в запросе.
"QTY": "Недостаточно бумаг на торговом счёте депо",
"PLACE": "Неверное место хранения (не наш торговый счёт/раздел в НРД)",
}
// m2mCodeText возвращает только человеческий текст кода (без самого кода).
// Для неизвестного кода — пустую строку.
func m2mCodeText(code string) string {
return m2mCodeTexts[strings.ToUpper(strings.TrimSpace(code))]
}
// rejectCodesHuman рендерит коды отказа как «M2M01 (словесное описание)» через
// «; ». Для отчёта регулятору — чтобы рядом с номером кода была расшифровка.
func rejectCodesHuman(codes []string) string {
var parts []string
for _, c := range codes {
c = strings.TrimSpace(c)
if c == "" {
continue
}
if t := m2mCodeText(c); t != "" {
parts = append(parts, c+" ("+t+")")
} else {
parts = append(parts, c)
}
}
return strings.Join(parts, "; ")
}
// m2mCodeLabel возвращает «Человеческий текст (M2Mxx)». Для неизвестного кода —
// сам код. Используется в шаблонах для понятного отображения кодов.
func m2mCodeLabel(code string) string {
code = strings.TrimSpace(code)
if code == "" {
return ""
}
if t := m2mCodeText(code); t != "" {
return t + " (" + code + ")"
}
return code
}
// m2mRejectOptions — варианты кодов отказа для формы решения по входящим
// (в порядке полезности). Каждый — {code, label}.
func m2mRejectOptions() []struct{ Code, Label string } {
codes := []string{"M2M05", "M2M01", "M2M02", "M2M03", "M2M04", "M2M06", "M2M07", "M2M08", "M2M09"}
out := make([]struct{ Code, Label string }, 0, len(codes))
for _, c := range codes {
out = append(out, struct{ Code, Label string }{c, m2mCodeLabel(c)})
}
return out
}