Files
Bridge-and-Join-s/internal/lkgateway/letters.go
T
fontvielle 50e420f4a0 DEPO на Firebird в мастере: подготовка базы одним действием, как для MSSQL
Мастер, шаг «Учёт DEPO», и «Обмен с DEPO» в настройках: СУБД «Firebird» (Fancy на Firebird 2.5).
- Подключение: драйвер nakagami/firebirdsql, путь к файлу базы или псевдоним, WIN1251. Запросы bj к DEPO
  (T-SQL) подключение переводит само (fancyfb.go): механически (dbo., TOP, ISNULL, GETDATE, LEN, N, exec)
  вне строк и комментариев, точные тексты — fsql/fb.go (каталоги, LIKE [_], COUNT(*) OVER, SELECT без FROM,
  PATINDEX); @имена — позиционные ?, GUID — в верхнем регистре. Системные запросы из кода — в fsql/depocatalog.go.
- «Подготовить» логином SYSDBA (depo_setup_fb.go): проверка базы и UDF Fancy, логин SYSDBA_BJ (Fancy даёт права
  на документы логинам SYSDBA%; для Firebird — обычный пользователь), оператор-робот Fancy, кодировщик M2M,
  привязки FTV_M2M_SETTINGS по признакам Fancy, таблицы, процедуры, права; обработку запускает bj.
  «Получить скрипт» — то же для isql в WIN1251; письмо администратору, установка процедур, плашка обновления.
- Скрипты Firebird встроены (depoprocs/firebird, тест сверяет с deploy/); количества — DOUBLE PRECISION: из
  диалекта 3 в базу диалекта 1 процедуру с NUMERIC(18,7) не создать.
- Проверено на копии базы Fancy (отдельный экземпляр bj): мастер с нуля — все проверки, BJ_PING; тестовый режим
  DEPO — К1 Бубликов, зачисление и списание до исполнения без ошибок; очередь, справочник НРД.
  Тест FBLive готовит на базе все запросы bj к DEPO: 94 из 101, оставшиеся 7 — ЛК и ручной ввод (следующий этап).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-27 18:55:02 +03:00

150 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
// Готовые письма тем, без кого подключение не закончить (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": // коротко и по делу: о внутреннем устройстве (bj, процедуры, классы) поставщику знать не нужно
return Letter{Key: key, Title: "Письмо в ARQA Technologies: модуль неторговых поручений", To: "ARQA Technologies, служба поддержки QUIK",
Subject: "Подключение модуля неторговых поручений QUIK — " + name,
Body: fmt.Sprintf("В ARQA Technologies\n(служба поддержки QUIK)\n\n%s (ИНН %s) просит:\n\n1. Подключить модуль неторговых поручений QUIK (переводы ценных бумаг M2M).\n2. Выдать логин и пароль для подключения к базе данных модуля неторговых поручений.\n3. Разрешить подключение к базе данных модуля с нашего адреса %s и сообщить адрес сервера, порт и имя базы.%s",
name, inn, arqaAddr(publicIP()), sign)}, true
case "dba":
ip := pickStr(outboundIP(s.Fancy.Server, s.Fancy.Port), firstServerIP())
f := s.Fancy
if f.IsFirebird() {
return Letter{Key: key, Title: "Письмо администратору базы DEPO", To: "Администратор сервера Firebird учёта DEPO (Fancy)",
Subject: "Bridge&Join: подготовка базы DEPO на сервере " + orBracket(f.Server, "сервер DEPO"),
Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы Bridge&Join к учёту DEPO (Fancy на Firebird) нужно на сервере %s выполнить один скрипт %s под SYSDBA утилитой isql с кодировкой WIN1251 — команда запуска в начале скрипта (файл прилагается, он в кодировке WIN1251). Скрипт:\n— создаёт пользователя сервера Firebird для bj (%s) с паролем из скрипта; права у него — только выданные скриптом: чтение учётных таблиц, запись в таблицы обмена FTV_M2M*, запуск процедуры обработки FTV_M2M_JOB;\n— заводит этот логин в Fancy оператором-роботом (по нему Fancy нумерует документы) и кодировщик M2M, если его нет;\n— создаёт таблицы обмена FTV_M2M*, процедуры обработки FTV_M2M_* и заполняет привязки к справочникам Fancy (таблица FTV_M2M_SETTINGS); учётные данные не меняет.\nВ конце скрипт показывает привязки: пустые значения нужно вписать вручную.\nСкрипт можно запускать повторно. В нём пароль логина bj — после выполнения файл удалите.\n\nТакже нужно разрешить подключение к серверу Firebird (порт %d) с сервера Bridge&Join (адрес %s).%s",
orBracket(f.Server, "сервер DEPO"), depoSetupFileName(f), depoSetupLogin(f), f.DefaultPort(), orBracket(ip, "IP-адрес сервера bj"), sign)}, true
}
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
}
// arqaAddr — наш внешний адрес для письма в ARQA с пометкой для отправителя: подключение разрешат только
// с этого адреса, поэтому он должен быть постоянным (у провайдера адрес может меняться). Пометку перед
// отправкой удаляют — как и всё в квадратных скобках.
func arqaAddr(ip string) string {
if ip == "" {
return "[постоянный внешний IP-адрес вашего сервера — уточните у сетевого администратора]"
}
return ip + " [убедитесь, что это постоянный внешний адрес вашей организации и он не меняется — уточните у сетевого администратора; пометку удалите]"
}
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))
}