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

134 lines
11 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
// Готовые письма тем, без кого подключение не закончить (05.10.2026): НРД (доступ к тестовому и боевому
// контурам), ARQA (модуль неторговых поручений QUIK), администратору базы DEPO. Реквизиты подставляются
// из настроек; то, чего bj не знает (контактное лицо), — в квадратных скобках. У НРД бывают свои формы
// заявок — письмо тогда идёт сопроводительным.
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"strings"
)
// Letter — готовое письмо.
type Letter struct {
Key, Title, To, Subject, Body string
}
func letterOrg(s Settings) (name, inn, code string) {
inn = orgINN(s)
name = strings.TrimSpace(s.NSD.OrgName)
if name == "" && inn != "" { // наименование не введено — из справочника участников НРД по ИНН
raw, _ := readHandbook()
var hb struct {
Participants []HandbookEntry `json:"participants"`
}
_ = json.Unmarshal(raw, &hb)
for _, p := range hb.Participants {
if p.INN == inn {
name = pickStr(p.FullName, p.Name)
break
}
}
}
if name == "" && globalRC != nil {
name = licenseStatus(globalRC).Tenant
}
return pickStr(name, "[наименование организации]"), pickStr(inn, "[ИНН]"), pickStr(orgCode(s), "[код участника]")
}
func orBracket(v, what string) string { return pickStr(strings.TrimSpace(v), "["+what+"]") }
// letterFor — письмо по ключу: nsd-test | nsd-prod | arqa | dba.
func letterFor(s Settings, key string) (Letter, bool) {
name, inn, code := letterOrg(s)
sign := "\n\nКонтактное лицо: [ФИО, должность, телефон, e-mail]\n\nС уважением,\n[ФИО, должность]\n" + name
crypto := "ГОСТ"
if strings.HasSuffix(s.NSD.Profile, "rsa") {
crypto = "RSA"
}
req := fmt.Sprintf("— депозитарный код: %s\n— депозитарный счёт: %s, раздел: %s\n— программа обмена: Интеграционный шлюз НРД (ИШ), канал WSL, криптография %s\n— роль: депозитарий — участник переводов ценных бумаг M2M",
orBracket(s.NSD.DeponentCode, "депозитарный код"), orBracket(s.NSD.AccountID, "депозитарный счёт"), orBracket(s.NSD.SectionID, "раздел счёта"), crypto)
switch key {
case "nsd-test":
return Letter{Key: key, Title: "Письмо в НРД: доступ к тестовому контуру TEST3", To: "НКО АО НРД, служба поддержки электронного документооборота",
Subject: "Заявка на подключение к тестовому контуру TEST3 для переводов M2M — " + name,
Body: fmt.Sprintf("В НКО АО НРД\n(служба поддержки электронного документооборота)\n\n%s (ИНН %s, код участника %s) просит предоставить доступ к тестовому контуру TEST3 электронного документооборота НРД для обмена сообщениями о переводах ценных бумаг M2M.\n\nСведения для подключения:\n%s\n\nПросим также сообщить порядок получения тестового ключа электронной подписи (УЦ Московской биржи) и включить нас в тестирование сценариев переводов M2M.\n\nЕсли для подключения установлена форма заявки — просим её направить, а это письмо считать сопроводительным.%s",
name, inn, code, req, sign)}, true
case "nsd-prod":
return Letter{Key: key, Title: "Письмо в НРД: подключение к боевому контуру", To: "НКО АО НРД, служба поддержки электронного документооборота",
Subject: "Заявка на подключение к промышленному контуру для переводов M2M — " + name,
Body: fmt.Sprintf("В НКО АО НРД\n(служба поддержки электронного документооборота)\n\n%s (ИНН %s, код участника %s) завершила тестирование обмена сообщениями о переводах ценных бумаг M2M на контуре TEST3 и просит подключить промышленный контур электронного документооборота НРД.\n\nСведения для подключения:\n%s\n— ключ электронной подписи для промышленного контура: выдан УЦ Московской биржи [номер сертификата, срок действия]\n\nЕсли для подключения установлена форма заявки — просим её направить, а это письмо считать сопроводительным.%s",
name, inn, code, req, sign)}, true
case "arqa":
ip := pickStr(outboundIP(s.Quik.Server, s.Quik.Port), firstServerIP())
return Letter{Key: key, Title: "Письмо в ARQA Technologies: модуль неторговых поручений", To: "ARQA Technologies, служба поддержки QUIK",
Subject: "Подключение модуля неторговых поручений QUIK для переводов M2M — " + name,
Body: fmt.Sprintf("В ARQA Technologies\n(служба поддержки QUIK)\n\n%s (ИНН %s) подключает систему Bridge&Join для переводов ценных бумаг M2M через НРД. Система работает с модулем неторговых поручений (МНП) QUIK через его базу данных. Просим:\n\n1. Подключить и настроить модуль неторговых поручений для переводов M2M:\n — классы инструментов INSTROUTM2M (вывод ценных бумаг) и INSTRINM2M (ввод ценных бумаг);\n — типы неторговых поручений для M2M (100/101) в QAdmin;\n — справочник депозитариев-получателей M2MDepoRecipients: его заполняет внешняя система.\n2. Выдать SQL-логин для внешней системы с правами API модуля (роль InstrEu): процедуры sp_NewInstrSec, sp_SignInstrSec, cancel_instruction; таблицы Export, Contracts, M2MDepoRecipients.\n3. Разрешить подключение к базе данных МНП с сервера Bridge&Join (адрес %s) и сообщить адрес сервера, порт и имя базы.\n4. После первого заполнения справочника M2MDepoRecipients перезапустить модуль неторговых поручений: справочник читается при запуске.%s",
name, inn, orBracket(ip, "IP-адрес сервера bj"), sign)}, true
case "dba":
ip := pickStr(outboundIP(s.Fancy.Server, s.Fancy.Port), firstServerIP())
f := s.Fancy
login := "bj-depo-login-mssql.sql"
job := "3. Создать задание SQL Server Agent обработки обмена — скрипт bj-depo-job-mssql.sql. Задание вызывает процедуры обработки FTV_M2M_JOB и FTV_M2M_PROCESS_* (поставляются отдельно; устанавливаются в базу DEPO в порядке WIRING → OPERATION → EVENTS → CREATE → JOB)."
dbms := "SQL Server"
if f.IsPostgres() {
login, dbms = "bj-depo-login-postgres.sql", "PostgreSQL"
job = "3. Выдать логину bj право запускать процедуру обработки обмена (её вызывает сам bj) — строка в мастере, раздел «Учёт DEPO» → «Обработка». Процедуры обработки FTV_M2M_* поставляются отдельно."
}
return Letter{Key: key, Title: "Письмо администратору базы DEPO", To: "Администратор СУБД учёта DEPO",
Subject: "Bridge&Join: доступ к базе DEPO " + f.DepoDB(),
Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы переводов ценных бумаг Bridge&Join к учёту DEPO нужно на сервере %s (база %s):\n\n1. Создать логин для bj с минимальными правами — скрипт %s: чтение учётных таблиц и запись только в таблицы обмена.\n2. Создать таблицы обмена FTV_M2M* — скрипт %s. Учётные данные они не меняют.\n%s\n4. Разрешить подключение к %s с сервера Bridge&Join (адрес %s).\n\nСкрипты прилагаются (скачиваются в мастере настройки bj, раздел «Учёт DEPO»). Все скрипты можно запускать повторно: если объект уже есть, он не меняется.%s",
orBracket(f.Server, "сервер DEPO"), f.DepoDB(), login, depoTablesFileName(f), job, dbms, orBracket(ip, "IP-адрес сервера bj"), sign)}, true
}
return Letter{}, false
}
func firstServerIP() string {
_, ips := localAddrs()
for _, ip := range ips {
if ip.To4() != nil && !ip.IsLoopback() {
return ip.String()
}
}
return ""
}
// letterView — письмо для шаблона (по текущим настройкам).
func letterView(key string) *Letter {
if globalRC == nil {
return nil
}
if l, ok := letterFor(globalRC.Snapshot(), key); ok {
return &l
}
return nil
}
// GET /admin/setup/letters/<ключ>.txt — письмо файлом.
func registerLetters(mux *http.ServeMux, rc *RuntimeConfig) {
mux.HandleFunc("/admin/setup/letters/", func(w http.ResponseWriter, r *http.Request) {
key := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/admin/setup/letters/"), ".txt")
l, ok := letterFor(rc.Snapshot(), key)
if !ok {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Disposition", `attachment; filename="bj-letter-`+key+`.txt"`)
_, _ = fmt.Fprintf(w, "Кому: %s\r\nТема: %s\r\n\r\n%s\r\n", l.To, l.Subject, strings.ReplaceAll(l.Body, "\n", "\r\n"))
})
}
// mailtoLink — ссылка «Открыть в почте» (тема и текст письма; пробелы и переводы строк — %20 и %0A).
func mailtoLink(l *Letter) template.URL {
if l == nil {
return ""
}
return template.URL("mailto:?subject=" + url.PathEscape(l.Subject) + "&body=" + url.PathEscape(l.Body))
}