Files
Bridge-and-Join-s/internal/lkgateway/letters.go
T
zuevav 21d87fb530 Учёт DEPO: задание SQL Server Agent — своё у каждой базы (bj2depo_<база>), имя в мастере
На одном сервере бывает несколько баз DEPO (боевая и тестовая): общее имя
bj2depo давало ложную зелёную проверку — тестовой базе засчитывалось боевое
задание, а своё не создавалось.
- Имя задания — настройка DepoJobName; в мастере, пункт 3, поле «Имя задания
  SQL Server Agent», по умолчанию bj2depo_<имя базы> (меняется вместе с базой,
  пока его не поправили). Установки старше — bj2depo, как было.
- Скрипт до любых изменений останавливается, если задание с этим именем
  обрабатывает другую базу или эту базу уже обрабатывает задание с другим именем
  (иначе двойная обработка событий).
- Проверка задания сравнивает его базу (sp_help_jobstep) с базой bj.
Синтаксис проверен на SQL Server без выполнения (SET PARSEONLY); проверка
задания на боевом сервере — зелёная (bj2depo ↔ BJ_DEPO_DATA).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
2026-10-06 16:09:14 +03:00

135 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
what := "— создаёт задание SQL Server Agent «" + depoJobName(f) + "» для обработки обмена;\n"
run := "под логином с правами sysadmin (SSMS или sqlcmd)"
if f.IsPostgres() {
what = ""
run = "суперпользователем: psql -h <сервер> -U postgres -f " + depoSetupFileName(f)
} else if f.DepoJobMode == "bj" {
what = "— даёт логину bj право запускать процедуру обработки обмена;\n"
}
return Letter{Key: key, Title: "Письмо администратору базы DEPO", To: "Администратор СУБД учёта DEPO",
Subject: "Bridge&Join: подготовка базы DEPO на сервере " + orBracket(f.Server, "сервер DEPO"),
Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы Bridge&Join к учёту DEPO нужно на сервере %s выполнить один скрипт %s %s (файл прилагается — он скачивается в мастере настройки bj). Скрипт:\n— в начале — имя базы DEPO и пароль логина bj: проверьте их; скрипт сначала убедится, что это база DEPO (есть учётные таблицы OD_FACES и OD_RESTS), иначе остановится, ничего не меняя;\n— создаёт логин bj: права — чтение учётных таблиц DEPO и запись только в таблицы обмена FTV_M2M*;\n— создаёт таблицы обмена FTV_M2M*; учётные данные он не меняет;\n%s\nСкрипт можно запускать повторно. В нём пароль логина bj — после выполнения файл удалите.\n\nТакже нужно разрешить подключение к серверу базы с сервера Bridge&Join (адрес %s). Процедуры обработки обмена FTV_M2M_* поставляются отдельно.%s",
orBracket(f.Server, "сервер DEPO"), depoSetupFileName(f), run, what, 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))
}