package lkgateway // participant_form.go — анкета участника сервиса M2M (M2MTransferParticipantForm). // Требование НРД при подключении: зарегистрировать нашу организацию в справочнике // участников M2M, иначе сервис МОСТ отбивает запросы кодом M2M14 «Код ЭДО НРД // отправителя отсутствует в справочнике участников M2M». Здесь bj формирует // заполненную анкету (предзаполнение из автоопределения DEPO), даёт проверить // (валидация + показ XML в windows-1251) и отправляет её в НРД: в боевом контуре — // через ИШ пакетом #M2MPF (businessLine=MOST, подпись боевым контейнером на стороне // ИШ), либо запасным каналом — письмом на M2MOST@nsd.ru с вложением. import ( "context" "encoding/base64" "fmt" "html" "net/http" "regexp" "strings" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml" ) // participantFormRecipient — адрес НРД для регистрации участника M2M (обычный канал). const participantFormRecipient = "M2MOST@nsd.ru" var pfINNRe = regexp.MustCompile(`^\d{10}$`) var pfCodeRe = regexp.MustCompile(`^MC\d{10}$`) // isProdIGW — текущий канал ИШ боевой. Анкету участника в НРД шлём ТОЛЬКО в бою: // в тестовом/гостевом контуре отправка через шлюз заблокирована. func isProdIGW(channel string) bool { return strings.HasPrefix(strings.ToUpper(strings.TrimSpace(channel)), "PROD") } // participantFormData — поля анкеты (юридические реквизиты организации). type participantFormData struct { INN string FullName string ShortName string DisplayName string EngFull string EngShort string EngDisplay string DepoCode string // DepositoryPlace/ParticipantCode (MC+10) BrokerCode string // BrokerPlace/ParticipantCode (MC+10), опционально } // participantFormPrefill — предзаполнение из автоопределения DEPO и настроек НРД. func participantFormPrefill(rc *RuntimeConfig) participantFormData { var d participantFormData r := depoDetectResult() if r == nil && rc != nil && strings.TrimSpace(rc.Snapshot().Fancy.Server) != "" { // после перезапуска bj автоопределения в памяти нет — прочитать DEPO сейчас ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) if res, err := depoDetectRequisites(ctx, rc.Snapshot().Fancy); err == nil { lastDepoDetect.mu.Lock() lastDepoDetect.r = res lastDepoDetect.mu.Unlock() r = res } cancel() } if r != nil { // DEPO: полное — OD_U_FACES, краткое — OD_FACES.NAME, отображаемое = краткое (так советует НРД); // английские — только парой (полное + краткое), иначе их не указываем d.INN = strings.TrimSpace(r.OrgINN) d.FullName = strings.TrimSpace(pick(r.OrgFull, r.OrgName)) d.ShortName = strings.TrimSpace(r.OrgName) d.DisplayName = strings.TrimSpace(r.OrgName) if strings.TrimSpace(r.OrgEngFull) != "" { d.EngFull = strings.TrimSpace(r.OrgEngFull) d.EngShort = strings.TrimSpace(r.OrgEng) d.EngDisplay = d.EngShort } if d.DepoCode == "" { d.DepoCode = strings.TrimSpace(r.DeponentCode) } } if rc != nil { s := rc.Snapshot() // Реквизиты нашей организации из настроек НРД и справочника участников — // заполняем то, чего не дало автоопределение DEPO. if d.INN == "" { d.INN = orgINN(s) } if d.DepoCode == "" { d.DepoCode = strings.ToUpper(strings.TrimSpace(pick(s.NSD.DeponentCode, s.NSD.OrgCode))) } // Карточка нашей фирмы в справочнике участников НРД: по MC-коду, затем по ИНН. hb, ok := handbookByCode(orgCode(s)) if !ok && d.INN != "" { hb, ok = handbookByINN(d.INN) } if ok { // НРД сверяет наименования анкеты со своей клиентской базой (M2M37) — карточка в справочнике // НРД главнее DEPO: в DEPO «наименование» бывает кратким (21.10.2026 краткое ушло в FullName). // Английские — из DEPO: в справочнике НРД есть лишь краткое. if n := strings.TrimSpace(hb.FullName); n != "" { d.FullName = n } if n := strings.TrimSpace(hb.Name); n != "" && hb.FullName != "" { d.ShortName, d.DisplayName = n, n // отображаемое НРД советует как краткое } if hb.Kind == "broker" && d.BrokerCode == "" { d.BrokerCode = strings.ToUpper(strings.TrimSpace(hb.Code)) } } if d.FullName == "" { d.FullName = strings.TrimSpace(s.NSD.OrgName) } } // DisplayName/Short обязательны в анкете — подставим из того, что есть. if d.DisplayName == "" { d.DisplayName = strings.TrimSpace(pick(d.ShortName, d.FullName)) } if d.ShortName == "" { d.ShortName = strings.TrimSpace(pick(d.DisplayName, d.FullName)) } return d } // participantFormValidate — проверка обязательных полей и форматов. Пустой срез — ок. func participantFormValidate(d participantFormData) []string { var errs []string if !pfINNRe.MatchString(strings.TrimSpace(d.INN)) { errs = append(errs, "ИНН: нужно 10 цифр (юридическое лицо)") } if strings.TrimSpace(d.FullName) == "" { errs = append(errs, "Полное наименование: обязательно") } if strings.TrimSpace(d.DisplayName) == "" { errs = append(errs, "Отображаемое наименование (DisplayName): обязательно") } if !pfCodeRe.MatchString(strings.TrimSpace(d.DepoCode)) { errs = append(errs, "Код депозитария (DepositoryPlace): формат MC + 10 цифр") } if strings.TrimSpace(d.BrokerCode) != "" && !pfCodeRe.MatchString(strings.TrimSpace(d.BrokerCode)) { errs = append(errs, "Код брокера (BrokerPlace): формат MC + 10 цифр (или оставьте пустым)") } // Структурная проверка типами НРД (ИНН/коды). p := m2m.HandbookParticipant{ INN: m2m.OrganizationINN(strings.TrimSpace(d.INN)), Names: m2m.HandbookNames{Rus: m2m.HandbookRusName{FullName: d.FullName, ShortName: d.ShortName, DisplayName: d.DisplayName}}, } if c := strings.TrimSpace(d.DepoCode); c != "" { p.DepositoryPlace = &m2m.DepositoryPlaces{ParticipantCode: m2m.DeponentCode(c)} } if c := strings.TrimSpace(d.BrokerCode); c != "" { p.BrokerPlace = &m2m.BrokerPlaces{ParticipantCode: m2m.DeponentCode(c)} } if err := p.Validate(); err != nil && len(errs) == 0 { errs = append(errs, "Проверка НРД: "+err.Error()) } return errs } // pfAbbrRe — наименование начинается с сокращённой формы (ООО, АО…) — для полного это ошибка. var pfAbbrRe = regexp.MustCompile(`^(ООО|ОАО|ЗАО|ПАО|НАО|АО|АКБ|КБ|НКО|ИК)[\s"«]`) // pfAskNSD — текст запроса в НРД о точных наименованиях участника (ответ НРД — источник истины). func pfAskNSD(code string) string { if code == "" { code = "наш код участника" } return "Просим сообщить точное наименование, под которым участник " + code + " зарегистрирован в клиентской базе НРД: полное (FullName), сокращённое (ShortName), " + "а также английские полное и сокращённое, если они есть." } // pfNameCheck — что в наименованиях надо уточнить у НРД. НРД сверяет их со своей клиентской // базой (M2M37): точный источник — карточка нашей фирмы в справочнике НРД или ответ НРД; // данные DEPO — только черновик. func pfNameCheck(rc *RuntimeConfig, d participantFormData) []string { var w []string hb, ok := participantCard(rc, d.INN) switch { case ok && strings.TrimSpace(hb.FullName) != "": x := participantFormData{FullName: hb.FullName} pfNames(&x) if x.FullName != d.FullName { w = append(w, "Полное наименование отличается от карточки нашей фирмы в справочнике НРД: «"+x.FullName+"». НРД сверяет с ней — без причины не меняйте.") } case strings.TrimSpace(d.FullName) == "": w = append(w, "Полного наименования нет ни в DEPO, ни в справочнике НРД — уточните его у НРД и впишите как есть.") } if pfAbbrRe.MatchString(d.FullName) || (d.FullName != "" && d.FullName == d.ShortName) { w = append(w, "Полное наименование похоже на сокращённое — нужно полное («Общество с ограниченной ответственностью …»), как в клиентской базе НРД.") } if (d.EngShort != "" || d.EngDisplay != "") && d.EngFull == "" { w = append(w, "Английские: указано краткое без полного — уточните полное у НРД или оставьте английские пустыми.") } return w } // participantCard — карточка нашей фирмы в справочнике НРД: по MC-коду, затем по ИНН. func participantCard(rc *RuntimeConfig, inn string) (HandbookEntry, bool) { if rc == nil { return HandbookEntry{}, false } s := rc.Snapshot() if hb, ok := handbookByCode(orgCode(s)); ok { return hb, true } if inn = strings.TrimSpace(pick(inn, orgINN(s))); inn != "" { return handbookByINN(inn) } return HandbookEntry{}, false } // pfQuotes — кавычки как в клиентской базе НРД: прямые ". var pfQuotes = strings.NewReplacer("«", `"`, "»", `"`, "“", `"`, "”", `"`, "„", `"`) // pfNames — наименования к виду базы НРД (кавычки, лишние пробелы). func pfNames(d *participantFormData) { for _, p := range []*string{&d.FullName, &d.ShortName, &d.DisplayName, &d.EngFull, &d.EngShort, &d.EngDisplay} { *p = strings.Join(strings.Fields(pfQuotes.Replace(*p)), " ") } } // pfx — экранирование текста XML-элемента (только & < >); кавычки в тексте // оставляем литеральными, как в образце анкеты НРД. var pfXMLEsc = strings.NewReplacer("&", "&", "<", "<", ">", ">") func pfx(s string) string { return pfXMLEsc.Replace(strings.TrimSpace(s)) } // participantFormXML — анкета в виде XML-строки (UTF-8) по образцу НРД // (M2MTransferParticipantForm.xsd). CreationTimestamp — по МСК. func participantFormXML(d participantFormData) string { pfNames(&d) msk := time.FixedZone("МСК", 3*3600) ts := time.Now().In(msk).Format("2006-01-02T15:04:05") + "(МСК)" var b strings.Builder b.WriteString(`` + "\n") b.WriteString(`` + "\n") b.WriteString("\t" + ts + "\n") b.WriteString("\t\n") b.WriteString("\t\t" + pfx(d.INN) + "\n") b.WriteString("\t\t\n") b.WriteString("\t\t\t\n") b.WriteString("\t\t\t\t" + pfx(d.FullName) + "\n") if strings.TrimSpace(d.ShortName) != "" { b.WriteString("\t\t\t\t" + pfx(d.ShortName) + "\n") } b.WriteString("\t\t\t\t" + pfx(d.DisplayName) + "\n") b.WriteString("\t\t\t\n") if strings.TrimSpace(d.EngFull) != "" || strings.TrimSpace(d.EngDisplay) != "" { b.WriteString("\t\t\t\n") b.WriteString("\t\t\t\t" + pfx(d.EngFull) + "\n") if strings.TrimSpace(d.EngShort) != "" { b.WriteString("\t\t\t\t" + pfx(d.EngShort) + "\n") } disp := d.EngDisplay if strings.TrimSpace(disp) == "" { disp = d.EngShort } b.WriteString("\t\t\t\t" + pfx(disp) + "\n") b.WriteString("\t\t\t\n") } b.WriteString("\t\t\n") b.WriteString("\t\t\n") b.WriteString("\t\t\t" + pfx(d.DepoCode) + "\n") b.WriteString("\t\t\n") if strings.TrimSpace(d.BrokerCode) != "" { b.WriteString("\t\t\n") b.WriteString("\t\t\t" + pfx(d.BrokerCode) + "\n") b.WriteString("\t\t\n") } b.WriteString("\t\n") b.WriteString("\n") return b.String() } // participantFormBytes — анкета в windows-1251 (как требует НРД: кодировка из пролога). func participantFormBytes(d participantFormData) ([]byte, error) { return nsdxml.EncodeWindows1251([]byte(participantFormXML(d))) } // participantEmailBody — текст письма на M2MOST@nsd.ru (обычный канал регистрации). func participantEmailBody(d participantFormData) string { var b strings.Builder b.WriteString("Добрый день!\r\n\r\n") b.WriteString("Просим зарегистрировать нашу организацию в справочнике участников сервиса M2M (МОСТ).\r\n") b.WriteString("Заполненная анкета участника (M2MTransferParticipantForm) во вложении.\r\n\r\n") b.WriteString("Реквизиты:\r\n") b.WriteString(" - Депозитарный код участника: " + strings.TrimSpace(d.DepoCode) + "\r\n") if strings.TrimSpace(d.BrokerCode) != "" { b.WriteString(" - Брокерский код участника: " + strings.TrimSpace(d.BrokerCode) + "\r\n") } b.WriteString(" - Полное наименование: " + strings.TrimSpace(d.FullName) + "\r\n") b.WriteString(" - ИНН: " + strings.TrimSpace(d.INN) + "\r\n\r\n") b.WriteString("После регистрации просим подтвердить.\r\n\r\n") b.WriteString("С уважением,\r\n" + strings.TrimSpace(d.ShortName) + "\r\n") return b.String() } // buildMIMEAttach — MIME multipart/mixed: текст письма (UTF-8) + вложение (base64). func buildMIMEAttach(from string, to []string, subject, body, filename string, attach []byte, ctype string) []byte { boundary := fmt.Sprintf("bjpart_%d", time.Now().UnixNano()) var b strings.Builder b.WriteString("From: " + from + "\r\n") b.WriteString("To: " + strings.Join(to, ", ") + "\r\n") b.WriteString("Subject: " + encodeHeaderWord(subject) + "\r\n") b.WriteString("MIME-Version: 1.0\r\n") b.WriteString("Content-Type: multipart/mixed; boundary=\"" + boundary + "\"\r\n\r\n") b.WriteString("--" + boundary + "\r\n") b.WriteString("Content-Type: text/plain; charset=UTF-8\r\n") b.WriteString("Content-Transfer-Encoding: 8bit\r\n\r\n") b.WriteString(body + "\r\n") b.WriteString("--" + boundary + "\r\n") b.WriteString("Content-Type: " + ctype + "; name=\"" + filename + "\"\r\n") b.WriteString("Content-Transfer-Encoding: base64\r\n") b.WriteString("Content-Disposition: attachment; filename=\"" + filename + "\"\r\n\r\n") enc := base64.StdEncoding.EncodeToString(attach) for i := 0; i < len(enc); i += 76 { j := i + 76 if j > len(enc) { j = len(enc) } b.WriteString(enc[i:j] + "\r\n") } b.WriteString("--" + boundary + "--\r\n") return []byte(b.String()) } // sendSMTPAttach — письмо с вложением (переиспользует транспорт smtpDeliver). func sendSMTPAttach(s SMTPSettings, subject, body, filename string, attach []byte, ctype string) error { if strings.TrimSpace(s.Host) == "" { return fmt.Errorf("не задан хост SMTP") } if strings.TrimSpace(s.From) == "" { return fmt.Errorf("не задан отправитель (From)") } rcpts := splitRecipients(s.To) if len(rcpts) == 0 { return fmt.Errorf("не заданы получатели (To)") } return smtpDeliver(s, rcpts, buildMIMEAttach(s.From, rcpts, subject, body, filename, attach, ctype)) } // registerParticipantForm — страница «Регистрация участника M2M»: форма, проверка, // скачивание анкеты, отправка письмом на M2MOST@nsd.ru. func registerParticipantForm(mux *http.ServeMux, rc *RuntimeConfig, igwClient *igw.Client, igwChannel string) { mux.HandleFunc("/admin/setup/participant-form", func(w http.ResponseWriter, r *http.Request) { d := participantFormPrefill(rc) action := "" if r.Method == http.MethodPost { _ = r.ParseForm() action = r.FormValue("action") d = participantFormData{ INN: r.FormValue("inn"), FullName: r.FormValue("full"), ShortName: r.FormValue("short"), DisplayName: r.FormValue("display"), EngFull: r.FormValue("eng_full"), EngShort: r.FormValue("eng_short"), EngDisplay: r.FormValue("eng_display"), DepoCode: r.FormValue("depo"), BrokerCode: r.FormValue("broker"), } } pfNames(&d) // кавычки и пробелы — как в базе НРД, и в анкете, и в форме, и в статусе errs := participantFormValidate(d) if action == "download" { if len(errs) > 0 { http.Error(w, "анкета не прошла проверку: "+strings.Join(errs, "; "), http.StatusBadRequest) return } body, err := participantFormBytes(d) if err != nil { http.Error(w, "кодировка windows-1251: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/xml; charset=windows-1251") w.Header().Set("Content-Disposition", `attachment; filename="M2MTransferParticipantForm.xml"`) _, _ = w.Write(body) return } var flash, flashKind string if action == "send" { to := strings.TrimSpace(r.FormValue("to")) if to == "" { to = participantFormRecipient } switch { case len(errs) > 0: flash, flashKind = "Анкета не прошла проверку — исправьте поля ниже.", "err" default: cfg := rc.Snapshot() s := cfg.Alerts.SMTP if !s.Enabled || strings.TrimSpace(s.Host) == "" { flash, flashKind = "SMTP не настроен (раздел «Почта») — отправить письмо нельзя. Пока скачайте анкету и отправьте вручную.", "err" break } s.To = to body, encErr := participantFormBytes(d) if encErr != nil { flash, flashKind = "кодировка windows-1251: "+encErr.Error(), "err" break } subject := "Регистрация участника сервиса M2M (МОСТ), код " + strings.TrimSpace(d.DepoCode) if err := sendSMTPAttach(s, subject, participantEmailBody(d), "M2MTransferParticipantForm.xml", body, "application/xml; charset=windows-1251"); err != nil { flash, flashKind = "письмо не отправлено: "+err.Error(), "err" break } flash, flashKind = "Анкета отправлена письмом на "+to+" (с вложением). Ждём подтверждения регистрации от НРД.", "ok" } } if action == "send_igw" { switch { case len(errs) > 0: flash, flashKind = "Анкета не прошла проверку — исправьте поля ниже.", "err" case igwClient == nil || igwChannel == "": flash, flashKind = "Боевой ИШ не настроен (раздел «НРД», профиль prod-*). Отправка через шлюз недоступна — переключитесь в боевой контур.", "err" case !isProdIGW(igwChannel): flash, flashKind = "Сейчас выбран тестовый контур. Анкету участника шлём только в боевом контуре — переключитесь на профиль prod-* («НРД») и повторите.", "err" default: body, encErr := participantFormBytes(d) if encErr != nil { flash, flashKind = "кодировка windows-1251: "+encErr.Error(), "err" break } pkg, _ := nsdadapter.RouteToPackageType(nsdadapter.KindParticipantForm) zipBody, zErr := igw.PackXML(body, "M2MTransferParticipantForm.xml", string(pkg)) if zErr != nil { flash, flashKind = "упаковка пакета: "+zErr.Error(), "err" break } ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second) id, sErr := igwClient.SendPackage(ctx, igwChannel, string(pkg), zipBody) cancel() if sErr != nil { flash, flashKind = "отправка через ИШ не удалась: "+sErr.Error(), "err" break } flash, flashKind = "Анкета отправлена через ИШ ("+string(pkg)+", пакет id="+id+"). Ждём подтверждения регистрации от НРД.", "ok" addParticipantReg(participantReg{SentAt: time.Now(), PkgID: id, Name: d.FullName, Channel: igwChannel}) } } xmlPreview := participantFormXML(d) reg := participantRegStatus(rc) if ws := pfNameCheck(rc, d); len(ws) > 0 { code := "" if rc != nil { code = orgCode(rc.Snapshot()) } reg += `
Уточните у НРД:Запрос в НРД (можно скопировать):
` + html.EscapeString(pfAskNSD(code)) + `
` } participantFormRender(w, d, errs, action, flash, flashKind, xmlPreview, reg, igwClient != nil && igwChannel != "", isProdIGW(igwChannel)) }) } // participantRegStatus — подтверждение регистрации появлением в справочнике участников // M2M текущего контура: если наша организация (по MC-коду или ИНН) уже в справочнике — // анкета принята. Это второй сигнал, помимо ответного документа от НРД. func participantRegStatus(rc *RuntimeConfig) string { if rc == nil { return "" } var out strings.Builder // Последняя отправка анкеты и ответ НРД на неё (учёт в participant-reg.json). if reg := loadParticipantReg(); !reg.SentAt.IsZero() { out.WriteString(`
Анкета отправлена через ИШ ` + reg.SentAt.Format("02.01.2006 15:04") + `, пакет ` + html.EscapeString(reg.PkgID)) if reg.Name != "" { out.WriteString(`, наименование «` + html.EscapeString(reg.Name) + `»`) } out.WriteString(`. `) switch { case reg.RespAt.IsZero() && !reg.DeliveredAt.IsZero(): out.WriteString(`НРД получил анкету (квитанция ` + reg.DeliveredAt.In(listMSK).Format("02.01 15:04:05") + `, ошибок нет) — ответ по существу пока не пришёл.`) case reg.RespAt.IsZero(): out.WriteString(`Ответ НРД пока не получен.`) case strings.EqualFold(reg.Status, "ERROR"): out.WriteString(`НРД отклонил ` + reg.RespAt.Format("02.01 15:04") + `: ` + html.EscapeString(reg.RespText) + `. Исправьте указанное и отправьте снова.`) if containsStr(reg.Codes, "M2M37") { out.WriteString(` Наименование не совпало с клиентской базой НРД — запросите у НРД точные наименования (текст запроса ниже) и впишите их без изменений.`) } default: out.WriteString(`НРД принял ` + reg.RespAt.Format("02.01 15:04") + `. ` + html.EscapeString(reg.RespText)) } out.WriteString(`
`) } s := rc.Snapshot() contour := map[string]string{"prod": "боевого", "guest": "гостевого", "test3": "тестового"}[contourOf(currentNSDProfile())] if contour == "" { contour = "текущего" } _ = s in, known, at, n := nsdHandbookHasUs(rc) switch { case in: out.WriteString(`
В справочнике участников M2M, полученном от НРД (` + contour + ` контур, ` + at.In(listMSK).Format("02.01 15:04") + `), наша организация есть. Регистрация подтверждена.
`) case known: out.WriteString(fmt.Sprintf(`
В справочнике участников M2M, полученном от НРД (%s контур, %s, участников %d), нашей организации пока нет. `+ `После приёма анкеты НРД добавит её в справочник — bj получает его каждые несколько минут и сам отметит регистрацию.
`, contour, at.In(listMSK).Format("02.01 15:04"), n)) default: out.WriteString(`
Справочник участников M2M от НРД ещё не получен — проверить регистрацию по нему пока нельзя.
`) } return out.String() } func participantFormRender(w http.ResponseWriter, d participantFormData, errs []string, action, flash, flashKind, xmlPreview, regHTML string, igwConfigured, igwProd bool) { w.Header().Set("Content-Type", "text/html; charset=utf-8") inp := func(name, label, val, ph string) string { return `` } var vErr string if (action == "preview" || action == "send") && len(errs) > 0 { vErr = `
Проверка не пройдена:
` } else if action == "preview" { vErr = `
Проверка пройдена — анкета корректна.
` } var flashHTML string if flash != "" { cls := "ok" if flashKind == "err" { cls = "bad" } flashHTML = `
` + html.EscapeString(flash) + `
` } igwBtn := `` igwStatus := `

Боевой контур: да — анкету можно отправить через шлюз (#M2MPF).

` if !igwProd { igwBtn = `` if igwConfigured { igwStatus = `

Контур тестовый — анкету участника в НРД шлём только в боевом контуре. Переключитесь на профиль prod-* («НРД»). Пока доступны «Скачать» и «Отправить письмом».

` } else { igwStatus = `

Боевой ИШ: не настроен (раздел «НРД», профиль prod-*). Пока доступны «Скачать» и «Отправить письмом».

` } } page := `Регистрация участника M2M

Регистрация участника сервиса M2M (МОСТ)

Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M. Без регистрации сервис отбивает запросы кодом M2M14. В боевом контуре анкета подаётся через ИШ пакетом #M2MPF (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на ` + participantFormRecipient + ` оставлено запасным каналом. Наименования должны точно совпадать с клиентской базой НРД (иначе M2M37): bj берёт их из карточки нашей фирмы в справочнике НРД и из DEPO (полное — карточка юрлица). Чего нет или в чём bj не уверен — уточните у НРД. Кавычки — прямые ".

` + regHTML + flashHTML + vErr + `
Организация ` + inp("inn", "ИНН (10 цифр)", d.INN, "7700000000") + ` ` + inp("full", "Полное наименование (как в клиентской базе НРД)", d.FullName, "Общество с ограниченной ответственностью …") + `
` + inp("short", "Сокращённое", d.ShortName, "") + inp("display", "Отображаемое (DisplayName)", d.DisplayName, "") + `
Английские наименования (опционально) ` + inp("eng_full", "Full name", d.EngFull, "") + `
` + inp("eng_short", "Short name", d.EngShort, "") + inp("eng_display", "Display name", d.EngDisplay, "") + `
Коды участника (MC + 10 цифр)
` + inp("depo", "Код депозитария (DepositoryPlace)", d.DepoCode, "MC0000000000") + inp("broker", "Код брокера (BrokerPlace, если брокер)", d.BrokerCode, "оставьте пустым, если не брокер") + `
Отправка ` + igwStatus + inp("to", "Кому (НРД), запасной канал", participantFormRecipient, participantFormRecipient) + `
` + igwBtn + `

Предпросмотр анкеты (windows-1251)

` + html.EscapeString(xmlPreview) + `
` _, _ = w.Write([]byte(page)) } func escapeAll(ss []string) []string { out := make([]string, len(ss)) for i, s := range ss { out[i] = html.EscapeString(s) } return out }