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(`
Коды, которыми сервис МОСТ и зачисляющий депозитарий сообщают о причине отказа или ошибки при переводе ценных бумаг между депозитариями.
`) section := func(title, note string, cat []m2mCodeInfo) { b.WriteString(`` + note + `
`) } b.WriteString(`| Код | Кратко | Что это значит |
|---|---|---|
| ` + esc(ci.Code) + ` | ` + esc(ci.Short) + ` | ` + esc(ci.Full) + ` |
Прочие коды сервиса (M2M11–M2M13, M2M16, M2M20–M2M29, M2M32–M2M43) определяются Спецификацией электронных документов НРД (Приложение 5) и в данной справке не расшифрованы.
`) if standalone { b.WriteString(``) } 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 }