From 67e81e5d7f2805b2a0a7d9ab607a3db4108d489c Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 14 May 2026 13:53:37 +0300 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BA=D0=B0=20=C2=AB=D0=98=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=C2=BB=20+=20=D1=80=D1=83=D1=81=D0=B8=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=81=D0=BE=D0=B2=20=D0=B2=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В admin-панели lk-gateway добавлен раздел /admin/help — справка по основным интеграциям, читается прямо на сервере, без выхода во внешнюю документацию. Состав /admin/help: - /admin/help — hub-страница с 4 карточками-ссылками - /admin/help/database — подключение PostgreSQL, схемы fansy/fansy_staging/m2m_core, что подгружается через ETL Fansy и что пишет сама система, роли, миграции, полезные запросы - /admin/help/lk-api — REST-контракт ESIA Finance V1: аутентификация, POST/GET/PATCH/list, формат callback'ов и ошибок, эмулятор для тестов, примеры curl - /admin/help/cryptopro — установка КриптоПро CSP на РЕД ОС и Ubuntu, ввод серийного номера лицензии (cpconfig), путь к PKCS#11 модулю libcppkcs11.so, подключение Рутокен ЭЦП 2.0 для подписи оператора, тестирование подписи через csptest и cryptcp - /admin/help/systems — Интеграционный шлюз НРД (профили guest/test3/prod), команда Fansy (порядок согласования контракта), уведомления (SMTP/Yandex Messenger/Telegram), контакты команд Русификация статусов: - Добавлены template-функции ruState и ruOutcome (в lkgateway и lkemulator) - "draft" → "Черновик", "confirmed" → "Подтверждена", "rejected" → "Отклонена" и т.д. - CSS-классы бейджей сохраняются (по исходному state), меняется только отображаемый текст. Технические термины (PostgreSQL, ИНН, GUID, REST) остаются как есть — они являются именами программного обеспечения. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/lkemulator/server.go | 36 ++++- internal/lkemulator/web/templates/claim.html | 4 +- internal/lkemulator/web/templates/home.html | 2 +- internal/lkgateway/admin.go | 92 ++++++++++++- .../lkgateway/web/templates/admin_claim.html | 8 +- .../lkgateway/web/templates/admin_claims.html | 2 +- .../lkgateway/web/templates/admin_help.html | 33 +++++ .../web/templates/admin_help_cryptopro.html | 113 ++++++++++++++++ .../web/templates/admin_help_database.html | 126 ++++++++++++++++++ .../web/templates/admin_help_lk.html | 93 +++++++++++++ .../web/templates/admin_help_systems.html | 76 +++++++++++ .../lkgateway/web/templates/admin_home.html | 2 +- 12 files changed, 574 insertions(+), 13 deletions(-) create mode 100644 internal/lkgateway/web/templates/admin_help.html create mode 100644 internal/lkgateway/web/templates/admin_help_cryptopro.html create mode 100644 internal/lkgateway/web/templates/admin_help_database.html create mode 100644 internal/lkgateway/web/templates/admin_help_lk.html create mode 100644 internal/lkgateway/web/templates/admin_help_systems.html diff --git a/internal/lkemulator/server.go b/internal/lkemulator/server.go index 67e1149..eb6b434 100644 --- a/internal/lkemulator/server.go +++ b/internal/lkemulator/server.go @@ -37,10 +37,44 @@ type Server struct { mux *http.ServeMux } +// templateFuncs — функции, доступные внутри шаблонов эмулятора +// (русификация статусов). +var templateFuncs = template.FuncMap{ + "ruState": ruState, +} + +// ruState — те же значения, что и в lkgateway.russianState. Дублирование +// допустимое: lkemulator — отдельный пакет и не зависит от lkgateway. +func ruState(s string) string { + switch s { + case "draft": + return "Черновик" + case "validated": + return "Валидирована" + case "submitted_to_nsd": + return "Отправлена в НРД" + case "awaiting_decision": + return "Ожидает решение" + case "confirmed": + return "Подтверждена" + case "awaiting_sub16": + return "Ожидает SUB16" + case "done": + return "Завершена" + case "rejected": + return "Отклонена" + case "timed_out": + return "Таймаут SLA" + case "manual_approval": + return "На ручном разборе" + } + return s +} + // NewServer собирает Server. func NewServer(cfg ServerConfig) (*Server, error) { parse := func(content string) (*template.Template, error) { - return template.ParseFS(tplFS, "web/templates/layout.html", "web/templates/"+content) + return template.New("layout.html").Funcs(templateFuncs).ParseFS(tplFS, "web/templates/layout.html", "web/templates/"+content) } home, err := parse("home.html") if err != nil { diff --git a/internal/lkemulator/web/templates/claim.html b/internal/lkemulator/web/templates/claim.html index 926a1f6..1dfcba9 100644 --- a/internal/lkemulator/web/templates/claim.html +++ b/internal/lkemulator/web/templates/claim.html @@ -1,6 +1,6 @@ {{define "content"}}
-

Заявка {{slice .Claim.ID 0 8}} · {{.Claim.Status}}

+

Заявка {{slice .Claim.ID 0 8}} · {{ruState .Claim.Status}}

Создана
{{.Claim.CreatedAt.Format "02.01.2006 15:04:05"}}
Обновлена
{{.Claim.UpdatedAt.Format "02.01.2006 15:04:05"}}
@@ -30,7 +30,7 @@

Полученный callback от lk-gateway

-
Новый статус
{{.Claim.LastCallback.NewStatus}}
+
Новый статус
{{ruState .Claim.LastCallback.NewStatus}}
{{if .Claim.LastCallback.ReasonCode}}
Код причины
{{.Claim.LastCallback.ReasonCode}} {{.Claim.LastCallback.ReasonText}}
{{end}} diff --git a/internal/lkemulator/web/templates/home.html b/internal/lkemulator/web/templates/home.html index 285c3d1..58c5a87 100644 --- a/internal/lkemulator/web/templates/home.html +++ b/internal/lkemulator/web/templates/home.html @@ -17,7 +17,7 @@ {{.SecuritiesCount}} {{.TransferringDepositoryINN}} {{.ReceivingDepositoryINN}} - {{.Status}} + {{ruState .Status}} детали → {{end}} diff --git a/internal/lkgateway/admin.go b/internal/lkgateway/admin.go index 35cfc15..d1379a7 100644 --- a/internal/lkgateway/admin.go +++ b/internal/lkgateway/admin.go @@ -19,12 +19,65 @@ var templatesFS embed.FS // конкретный content-шаблон). Так html/template не путается с несколькими // {{define "content"}} в разных файлах. type admin struct { - home, claims, claim, status, setup *template.Template + home, claims, claim, status, setup *template.Template + help, helpDatabase, helpLK, helpCryptoPro, helpSystems *template.Template } +// templateFuncs — функции, доступные внутри шаблонов. Главная задача — +// русификация статусов и других технических обозначений (см. требование +// «всё UI на русском, кроме программных терминов»). +var templateFuncs = template.FuncMap{ + "ru": russianText, + "ruState": russianState, + "ruOutcome": russianOutcome, +} + +// russianState переводит технический FSM-state в человекочитаемый +// русский, сохраняя CSS-класс для бейджа. +func russianState(s string) string { + switch s { + case "draft": + return "Черновик" + case "validated": + return "Валидирована" + case "submitted_to_nsd": + return "Отправлена в НРД" + case "awaiting_decision": + return "Ожидает решение" + case "confirmed": + return "Подтверждена" + case "awaiting_sub16": + return "Ожидает SUB16" + case "done": + return "Завершена" + case "rejected": + return "Отклонена" + case "timed_out": + return "Таймаут SLA" + case "manual_approval": + return "На ручном разборе" + } + return s +} + +// russianOutcome — для NSDDecisionSecurity.Outcome. +func russianOutcome(o string) string { + switch o { + case "confirmed": + return "Подтверждено" + case "rejected": + return "Отказ" + } + return o +} + +// russianText — fallback функция для произвольных строк (на случай +// будущих расширений). Сейчас возвращает строку без изменений. +func russianText(s string) string { return s } + func newAdmin() (*admin, error) { parse := func(content string) (*template.Template, error) { - return template.ParseFS(templatesFS, + return template.New("layout.html").Funcs(templateFuncs).ParseFS(templatesFS, "web/templates/layout.html", "web/templates/"+content) } @@ -48,7 +101,30 @@ func newAdmin() (*admin, error) { if err != nil { return nil, fmt.Errorf("parse admin_setup: %w", err) } - return &admin{home: home, claims: claims, claim: claim, status: status, setup: setup}, nil + help, err := parse("admin_help.html") + if err != nil { + return nil, fmt.Errorf("parse admin_help: %w", err) + } + helpDB, err := parse("admin_help_database.html") + if err != nil { + return nil, fmt.Errorf("parse admin_help_database: %w", err) + } + helpLK, err := parse("admin_help_lk.html") + if err != nil { + return nil, fmt.Errorf("parse admin_help_lk: %w", err) + } + helpCP, err := parse("admin_help_cryptopro.html") + if err != nil { + return nil, fmt.Errorf("parse admin_help_cryptopro: %w", err) + } + helpSys, err := parse("admin_help_systems.html") + if err != nil { + return nil, fmt.Errorf("parse admin_help_systems: %w", err) + } + return &admin{ + home: home, claims: claims, claim: claim, status: status, setup: setup, + help: help, helpDatabase: helpDB, helpLK: helpLK, helpCryptoPro: helpCP, helpSystems: helpSys, + }, nil } // page — общий "конверт" данных для всех шаблонов. @@ -111,6 +187,16 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, getOpts func() CheckOptions a.renderClaim(w, r, svc, id) case p == "status": a.renderStatus(w, r, getOpts()) + case p == "help": + render(w, a.help, nowPage("Инструкции", "help")) + case p == "help/database": + render(w, a.helpDatabase, nowPage("База данных", "help")) + case p == "help/lk-api": + render(w, a.helpLK, nowPage("API ЛК", "help")) + case p == "help/cryptopro": + render(w, a.helpCryptoPro, nowPage("КриптоПро", "help")) + case p == "help/systems": + render(w, a.helpSystems, nowPage("Внешние системы", "help")) default: http.NotFound(w, r) } diff --git a/internal/lkgateway/web/templates/admin_claim.html b/internal/lkgateway/web/templates/admin_claim.html index 51939cd..483820b 100644 --- a/internal/lkgateway/web/templates/admin_claim.html +++ b/internal/lkgateway/web/templates/admin_claim.html @@ -1,6 +1,6 @@ {{define "content"}}
-

Заявка {{slice .Claim.ID 0 8}} · {{.Claim.Status}}

+

Заявка {{slice .Claim.ID 0 8}} · {{ruState .Claim.Status}}

@@ -39,7 +39,7 @@ {{range .Claim.Stages}} - + @@ -74,7 +74,7 @@ {{range .Claim.M2MDecision.Securities}} - + {{end}} @@ -87,7 +87,7 @@

Последний callback в ЛК

Создана{{.Claim.CreatedAt.Format "02.01.2006 15:04:05"}}
Обновлена{{.Claim.UpdatedAt.Format "02.01.2006 15:04:05"}}
{{.State}}{{ruState .State}} {{.EnteredAt.Format "15:04:05.000"}} {{if .LeftAt}}{{.LeftAt.Format "15:04:05.000"}}{{else}}сейчас{{end}} {{.Reason}}
{{.ReferenceID}}{{.Outcome}}{{ruOutcome .Outcome}} {{range .RejectCodes}}{{.}} {{end}}
- + {{if .Claim.LastCallback.ReasonCode}} {{end}} diff --git a/internal/lkgateway/web/templates/admin_claims.html b/internal/lkgateway/web/templates/admin_claims.html index 4a4d358..c44fb90 100644 --- a/internal/lkgateway/web/templates/admin_claims.html +++ b/internal/lkgateway/web/templates/admin_claims.html @@ -14,7 +14,7 @@ - + {{end}} diff --git a/internal/lkgateway/web/templates/admin_help.html b/internal/lkgateway/web/templates/admin_help.html new file mode 100644 index 0000000..d37fbdf --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help.html @@ -0,0 +1,33 @@ +{{define "content"}} +
+

Инструкции и подсказки

+

Краткие гайды по основным интеграциям и эксплуатации Bridge-and-Join-s. Открывайте раздел и быстро находите команду или объяснение.

+
+ + +{{end}} diff --git a/internal/lkgateway/web/templates/admin_help_cryptopro.html b/internal/lkgateway/web/templates/admin_help_cryptopro.html new file mode 100644 index 0000000..589453d --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help_cryptopro.html @@ -0,0 +1,113 @@ +{{define "content"}} +

← все инструкции

+ +
+

КриптоПро и Рутокен

+

Bridge-and-Join-s использует ГОСТ Р 34.10-2012 для подписи и проверки XMLDSig. Серверная криптография — КриптоПро CSP. Подпись оператора в admin-ui — Рутокен ЭЦП 2.0 (опционально). Оба продукта говорят со стандартным интерфейсом PKCS#11, поэтому Go-клиент общается с ними одинаково.

+
+ +
+

1. Что и зачем нужно

+
Статус{{.Claim.LastCallback.NewStatus}}
Статус{{ruState .Claim.LastCallback.NewStatus}}
Код причины{{.Claim.LastCallback.ReasonCode}} {{.Claim.LastCallback.ReasonText}}
{{len .Securities}} {{.TransferringDepositoryINN}} {{.ReceivingDepositoryINN}}{{.Status}}{{ruState .Status}} детали →
+ + + + + + + +
СценарийСКЗИЦена (ориентир)
Проверка XMLDSig входящих от НРД и брокеровКриптоПро CSP «Сервер»~30-50к ₽ (один раз)
Подпись пакетов в НРД (резервный канал WS ONYX)КриптоПро CSP «Сервер»включено
Подпись действий оператора в admin-uiРутокен ЭЦП 2.0 + лицензия CSP «Рабочее место»~3-5к ₽ железо + ~2-3к ₽ лицензия
Проверка XMLDSig заявлений от ЛККриптоПро CSP «Сервер»включено
+

Если используется Интеграционный шлюз НРД (ИШ), он сам подписывает пакеты — наша серверная подпись нужна только для резервного канала ONYX и подписи действий оператора. Можно начать с минимума: только Рутокен оператора и отложить серверную лицензию.

+
+ +
+

2. Установка КриптоПро CSP на РЕД ОС

+

Скачать дистрибутив с www.cryptopro.ru (Linux x86_64).

+
sudo rpm -i cprocsp-rdr-gui-gtk-64-5.0.*.rpm \
+            cprocsp-rdr-64-5.0.*.rpm \
+            cprocsp-ca-certs-64-5.0.*.rpm \
+            lsb-cprocsp-base-5.0.*.rpm \
+            lsb-cprocsp-rdr-64-5.0.*.rpm
+

Активация лицензии:

+
sudo /opt/cprocsp/sbin/amd64/cpconfig -license -set XXXX-XXXXX-XXXXX-XXXXX-XXXXX
+

Проверка:

+
/opt/cprocsp/sbin/amd64/cpconfig -license -view
+/opt/cprocsp/bin/amd64/csptest -keyset -enum -unique
+
+ +
+

3. Установка на Ubuntu / Debian

+
sudo dpkg -i cprocsp-rdr-gui-gtk-64_5.0.*_amd64.deb \
+             cprocsp-rdr-64_5.0.*_amd64.deb \
+             lsb-cprocsp-base_5.0.*_all.deb \
+             lsb-cprocsp-rdr-64_5.0.*_amd64.deb
+sudo apt-get install -f
+sudo /opt/cprocsp/sbin/amd64/cpconfig -license -set XXXX-XXXXX-XXXXX-XXXXX-XXXXX
+
+ +
+

4. PKCS#11 модуль

+

Путь к библиотеке после установки:

+
/opt/cprocsp/lib/amd64/libcppkcs11.so
+

Эта же библиотека работает и с CSP-ключами (контейнеры на диске или в реестре), и с Рутокен ЭЦП 2.0 (подключённым по USB или в виде smart-card reader).

+

На странице «Настройка» в карточке «Криптография» укажите:

+
    +
  • Провайдер: cryptopro
  • +
  • UDS-сокет: /run/bj/crypto.sock (для legacy crypto-service на Java — на M2+ переходим на Go-клиент напрямую через PKCS#11)
  • +
  • Путь к jcp.jar / PKCS#11: /opt/cprocsp/lib/amd64/libcppkcs11.so
  • +
  • Лицензионный ключ: серийный номер CSP
  • +
+
+ +
+

5. Подключение Рутокен ЭЦП 2.0

+

Подключите Рутокен в USB. Драйверы КриптоПро CSP уже включают поддержку Рутокен:

+
# увидеть подключённые токены
+/opt/cprocsp/bin/amd64/csptest -card -enum
+
+# увидеть ключевые контейнеры на токене
+/opt/cprocsp/bin/amd64/csptest -keyset -enum -unique
+

Для подписи действий оператора в admin-ui:

+
    +
  1. Запросить сертификат на физлицо у УЦ (через личный кабинет КриптоПро или через АРМ оператора УЦ).
  2. +
  3. Записать сертификат и контейнер на Рутокен.
  4. +
  5. На странице «Настройка» в карточке «Криптография» выбрать провайдер cryptopro и указать слот Рутокен.
  6. +
+
+ +
+

6. Импорт сертификата

+
# сертификат корневого УЦ (если ещё нет в системе)
+/opt/cprocsp/bin/amd64/certmgr -inst -store mroot -file /path/to/root-ca.cer
+
+# сертификат подписанта (контейнер на токене)
+/opt/cprocsp/bin/amd64/certmgr -inst -store uMy -cont '\\.\HDIMAGE\my-keys' \
+    -file /path/to/operator.cer
+
+# проверить установленные сертификаты
+/opt/cprocsp/bin/amd64/certmgr -list -store uMy
+
+ +
+

7. Тестирование подписи

+

Через CLI КриптоПро (быстрая проверка что криптография работает):

+
# подписать произвольный файл
+/opt/cprocsp/bin/amd64/cryptcp -signf -dn 'CN=Иванов И.И.' \
+    -det -strict /tmp/test.txt
+
+# проверить подпись
+/opt/cprocsp/bin/amd64/cryptcp -vsignf -det /tmp/test.txt /tmp/test.txt.sgn
+

Через нашу систему — раздел Настройка → кнопка «Запустить тестовую заявку». На странице «Заявка» появится результат и расшифровка проверки подписи.

+
+ +
+

8. Поддержка

+
    +
  • Документация КриптоПро: www.cryptopro.ru/products/csp
  • +
  • Установка на РЕД ОС: www.cryptopro.ru/forum2/default.aspx?g=topics&f=43
  • +
  • Технические вопросы: support@cryptopro.ru
  • +
  • Рутокен: dev.rutoken.ru/display/PUB/Rutoken+EDS
  • +
+

При проблемах с лицензией сначала проверьте cpconfig -license -view — лицензия должна быть валидна и не просрочена. Срок действия КриптоПро лицензии — обычно 1 год.

+
+{{end}} diff --git a/internal/lkgateway/web/templates/admin_help_database.html b/internal/lkgateway/web/templates/admin_help_database.html new file mode 100644 index 0000000..90bf706 --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help_database.html @@ -0,0 +1,126 @@ +{{define "content"}} +

← все инструкции

+ +
+

База данных

+

PostgreSQL 16 (или PostgreSQL Pro Certified). Хранит сделки, журнал событий, справочники и данные, поступающие из внешних систем через ETL.

+
+ +
+

1. Подключение

+

DSN указывается в разделе Настройка → PostgreSQL. Формат:

+
postgres://USER:PASSWORD@HOST:PORT/DBNAME?sslmode=disable
+

Локальный дев-стенд:

+
postgres://bj:bj_dev@127.0.0.1:5432/bj?sslmode=disable
+

При сохранении выполняется pgx.Connect и Ping. Если БД недоступна, форма покажет ошибку.

+
+ +
+

2. Схемы

+ + + + + + + + + + + + + + + + + + + + + + +
СхемаНазначениеКто пишетКто читает
fansy_stagingПромежуточные таблицы для ETL FansyКоманда Fansy (роль fansy_etl)Наша процедура перелива в fansy
fansyРабочие данные: клиенты, документы, ИИС, депо-счета, портфели, справочники ЦБ и участниковНаша процедура перелива (после валидации)Наша система (роль bj_reader): m2m-core при формировании заявок
m2m_coreСделки M2M, журнал событий FSMbj-serverbj-server, admin-ui
+
+ +
+

3. Что подгружается из внешних систем

+

Команда Fansy через инкрементный UPSERT в staging-таблицы:

+ + + + + + + + + + +
Таблица stagingSLA свежестиЧто в ней
clients≤ 5 минутАнкеты инвесторов (ФИО, дата рождения, ИНН)
client_documents≤ 5 минутДокументы (паспорт, ИНН и др.)
iia_contracts≤ 5 минутДоговоры ИИС (тип T12/T03, номер, дата, ИНН брокера)
depo_accounts≤ 5 минутДепо-счета и разделы у разных депозитариев
portfolios≤ 1 минутаОстатки ЦБ (целое/дробное количество, статус обособления)
securities / participantsраз в суткиСправочники ЦБ и участников MOST
+

Полный data-dictionary: docs/fansy-contract/v1/data-dictionary.md. Требования к ETL: docs/fansy-contract/v1/etl-requirements.md.

+
+ +
+

4. Что пишет сама система

+

Только в схему m2m_core:

+
    +
  • deals — корневая запись сделки. Уникальность по guid = идемпотентность приёма заявок.
  • +
  • deal_events — журнал FSM-событий (event sourcing).
  • +
+

Старт сделки происходит при POST /api/v1/back_office/claims/. FSM проходит этапы:

+
draft → validated → submitted_to_nsd → awaiting_decision →
+    confirmed → awaiting_sub16 → done
+                            ↘ rejected
+                            ↘ timed_out
+                            ↘ manual_approval
+
+ +
+

5. Учётные записи и роли

+ + + + + + + +
РольПраваСоздаёт
fansy_etlSELECT/INSERT/UPDATE на fansy_staging.*миграция 000__roles.sql
bj_readerSELECT на fansy.*миграция 000__roles.sql
bj_migratorВладелец схем, DDL-правамиграция 000__roles.sql
+

Пароли проставляются администратором БД через ALTER ROLE, в репозиторий не попадают.

+
+ +
+

6. Накатывание миграций

+

Файлы лежат в migrations/fansy-store/ и migrations/m2m-core/. Применить локально:

+
cd /home/fontvielle/Bridge-and-Join-s
+for f in migrations/fansy-store/*.sql migrations/m2m-core/*.sql; do
+    podman exec -i bj-postgres psql -U bj -d bj -v ON_ERROR_STOP=1 < "$f"
+done
+

Порядок:

+
    +
  1. fansy-store/000__roles.sql — роли
  2. +
  3. fansy-store/001__schemas.sql — схемы и гранты
  4. +
  5. fansy-store/002__working.sql — рабочая схема
  6. +
  7. fansy-store/003__staging.sql — staging
  8. +
  9. fansy-store/004__seed_participants.sql — справочник участников (НРД, БКС, Ренессанс, Альфа-Банк)
  10. +
  11. m2m-core/001__deals.sql — сделки M2M
  12. +
  13. m2m-core/002__stages.sql — jsonb-колонка истории FSM
  14. +
+
+ +
+

7. Полезные запросы

+
-- Состояние сделок за последний час
+SELECT state, count(*) FROM m2m_core.deals
+WHERE created_at > now() - interval '1 hour'
+GROUP BY state;
+
+-- Журнал событий по сделке
+SELECT created_at, type, actor, payload
+FROM m2m_core.deal_events
+WHERE deal_id = '...'
+ORDER BY created_at;
+
+-- Свежесть данных Fansy
+SELECT 'portfolios' AS t, max(loaded_at) FROM fansy_staging.portfolios
+UNION ALL SELECT 'clients', max(loaded_at) FROM fansy_staging.clients;
+
+{{end}} diff --git a/internal/lkgateway/web/templates/admin_help_lk.html b/internal/lkgateway/web/templates/admin_help_lk.html new file mode 100644 index 0000000..ef9dc7f --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help_lk.html @@ -0,0 +1,93 @@ +{{define "content"}} +

← все инструкции

+ +
+

API личного кабинета (ESIA Finance V1)

+

Bridge-and-Join-s реализует контракт ESIA Finance V1 на стороне back-office. ЛК клиента отправляет нам заявку, мы возвращаем подтверждение и потом колбэк со статусом. Полная спецификация: docs/lk-contract/v1/openapi.yaml.

+
+ +
+

1. Аутентификация

+

HTTP Basic. Учётные записи и пароли согласуются с командой ЛК.

+
curl -u "lk-user:lk-password" \
+  -H "Content-Type: application/json" \
+  http://10.10.10.22:8080/api/v1/back_office/claims/
+

На M2 (дев-стенд) аутентификация отключена; включится на M3 — параллельно с подключением реального ЛК.

+
+ +
+

2. Создание заявки M2M

+

POST /api/v1/back_office/claims/

+

Тело — JSON, кодировка UTF-8. Минимально нужно: анкета инвестора, ИНН передающего и принимающего депозитария, информация о стоимости, массив ценных бумаг с количеством и счетами, подписанный XML заявления (base64).

+
curl -X POST http://10.10.10.22:8080/api/v1/back_office/claims/ \
+  -H "Content-Type: application/json" \
+  -d @docs/lk-contract/v1/examples/claim-request.json
+

Ответ 201 Created:

+
{
+  "id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
+  "status": "submitted_to_nsd",
+  "created_at": "2026-03-02T14:30:45Z",
+  "success": true
+}
+
+ +
+

3. Получение заявки и её статуса

+

GET /api/v1/back_office/claims/{id}

+
curl http://10.10.10.22:8080/api/v1/back_office/claims/c02a1d5e-c2af-4799-bab4-953f133c5133
+

Возвращает полную карточку с историей FSM, ответом НРД и решением принимающей стороны (когда оно пришло).

+
+ +
+

4. Список заявок

+

GET /api/v1/back_office/claims с query-фильтрами: status, investor_id, created_from, created_to, limit, offset.

+
curl "http://10.10.10.22:8080/api/v1/back_office/claims?status=confirmed&limit=20"
+
+ +
+

5. Callback статуса (мы → ЛК)

+

Когда сделка меняет статус (подтверждена принимающей стороной, отклонена, или произошёл таймаут), bj-server делает PATCH {LK_BASE}/api/v1/back_office/claims/{id}:

+
{
+  "claim_id": "c02a1d5e-c2af-4799-bab4-953f133c5133",
+  "new_status": "confirmed",
+  "updated_at": "2026-03-02T14:38:12Z",
+  "nsd_response": {
+    "guid": "...",
+    "status_code": "INFO",
+    "responses": [{"reference_id": "M2M2026...", "code": "01", "text": "..."}]
+  }
+}
+

Адрес ЛК указывается в Настройка → Callback в ЛК или через переменную BJ_LK_CALLBACK_URL.

+
+ +
+

6. Формат ошибок

+

Любая ошибка возвращается в формате, идентичном ESIA Finance V1:

+
{
+  "error": true,
+  "status": 422,
+  "code": "invalid_signature",
+  "title": "Подпись заявления не прошла проверку",
+  "meta": {
+    "message": "Сертификат подписанта недействителен или цепочка доверия не построена.",
+    "errors": [{"field": "signed_document", "message": "..."}]
+  }
+}
+
+ +
+

7. Эмулятор ЛК для тестов

+

На дев-стенде доступен lk-emulator на порту 8083 — имитация ЛК. Он сам регистрирует свой URL в bj-server как callback-приёмник.

+
    +
  • http://10.10.10.22:8083/ — журнал моих заявок (автообновление 3 сек)
  • +
  • http://10.10.10.22:8083/new — форма «подать заявку» с предустановленными инвесторами из seed-данных
  • +
  • При запуске реального ЛК эмулятор остаётся как QA-инструмент
  • +
+
+ +
+

8. Подписание заявления

+

ЛК должен подписать заявление XMLDSig (ГОСТ или RSA) и положить в поле signed_document (base64). Мы проверяем подпись через crypto-service — см. инструкцию по КриптоПро.

+

На M2 проверка подписи отключена (stub). На M3-M4 включится после подключения СКЗИ.

+
+{{end}} diff --git a/internal/lkgateway/web/templates/admin_help_systems.html b/internal/lkgateway/web/templates/admin_help_systems.html new file mode 100644 index 0000000..58fd4ff --- /dev/null +++ b/internal/lkgateway/web/templates/admin_help_systems.html @@ -0,0 +1,76 @@ +{{define "content"}} +

← все инструкции

+ +
+

Внешние системы

+

Как Bridge-and-Join-s взаимодействует с тремя главными внешними сторонами: НРД (через ИШ), команда Fansy (ETL в БД), уведомления операторам.

+
+ +
+

1. Интеграционный шлюз НРД (ИШ)

+

Основной канал отправки M2M-сообщений в НРД. ИШ сам подписывает пакеты ЭДО, нам криптография в этом канале не нужна.

+

Профили (см. Настройка → Интеграционный шлюз НРД):

+ + + + + + + + + + +
ПрофильСредаКриптография
guest-gostГостевой контур (без проверок)ГОСТ Р 34.10-2012
guest-rsaГостевой контурRSA
test3-gostТестовый контур TEST3ГОСТ
test3-rsaTEST3RSA
prod-gostПродуктивныйГОСТ
prod-rsaПродуктивныйRSA
+

Что указать в Настройка → ИШ:

+
    +
  • Профиль (например, test3-gost)
  • +
  • URL ИШ — обычно http://localhost:8080 если ИШ установлен на той же ВМ
  • +
  • Ключевой контейнер — имя на стороне ИШ, например TEST3_GOST_CONTAINER
  • +
+

Без настроенного ИШ система работает в mock-режиме: bj-server эмитирует синтетический Decision через 3 секунды для каждой заявки. Это удобно для дев-демо и не требует подключения к НРД.

+

Документация по подключению: DOC/instr_podkl_stend_v3.pdf, DOC/Ссылки для доступа в тестовые контуры.pdf.

+
+ +
+

2. Команда Fansy (ETL в БД)

+

Команда Fansy на своей стороне настраивает ETL, который пишет в схему fansy_staging.* нашей БД. Мы переливаем оттуда в fansy.* после валидации.

+

Что от вас как заказчика нужно:

+
    +
  1. Передать команде Fansy ссылку на каталог docs/fansy-contract/v1/ (тег fansy-contract-v1).
  2. +
  3. Согласовать SLA свежести и расписание (по умолчанию — портфели за 1 минуту, остальное за 5 минут, справочники раз в сутки).
  4. +
  5. Завести в БД учётную запись с ролью fansy_etl и передать команде Fansy. Пароль выдать через защищённый канал.
  6. +
  7. Прописать IP/подсеть Fansy в pg_hba.conf на стороне PostgreSQL (только TLS, sslmode=verify-full).
  8. +
+

Полный контракт: docs/fansy-contract/v1/etl-requirements.md. Семантика полей: docs/fansy-contract/v1/data-dictionary.md.

+
+ +
+

3. Уведомления операторам

+

В M3-M4 будет раздел Настройка → Уведомления. Архитектура — провайдеры-плагины с единым интерфейсом Notifier { Send(ctx, recipient, template, data) }:

+ + + + + + + + + +
ПровайдерНазначениеЧто вводить
SMTPE-mail (внутренний или внешний сервер)хост, порт, логин/пароль, from-адрес
Yandex Messenger (Yandex 360)Корпоративный мессенджер. У заказчика уже есть готовый бот — дотягиваем егоAPI-token, webhook, chat-id / user-id
TelegramОпционально для отдельных операторовbot-token, chat-id
Mattermost / Rocket.ChatКорпоративные мессенджеры (если используются)webhook URL
WebSocket в admin-uiМгновенный push если оператор открыл вкладкувстроено, без настроек
+

Логика маршрутизации: критичный этап (ручное согласование, >80% SLA, отказ НРД) → параллельно e-mail + Messenger + WS-push. Обычные события — только e-mail. Маршрутизация по ролям настраивается в UI.

+
+ +
+

4. Контакты команд

+ + + + + + + + + +
Внешняя сторонаЧто согласовать
НРД (Национальный расчётный депозитарий)Тестовые сертификаты GUEST/TEST3, дистрибутив ИШ, доступ к личному кабинету УЦ НРД
Команда ЛК (ESIA Finance)Базовый URL ЛК, Basic-auth учётные данные, очерёдность подключения (сначала эмулятор, потом реальный ЛК)
Команда FansyКонтракт docs/fansy-contract/v1/, SLA, окна обслуживания, IP-allowlist
КриптоПроСерийный номер лицензии CSP, актуальный дистрибутив, поддержка support@cryptopro.ru
Брокеры-контрагенты MOSTБКС (ИНН 5406121446), Ренессанс (7709258228), Альфа-Банк (7728168971) — уже в seed
+
+{{end}} diff --git a/internal/lkgateway/web/templates/admin_home.html b/internal/lkgateway/web/templates/admin_home.html index 638775a..61d00e2 100644 --- a/internal/lkgateway/web/templates/admin_home.html +++ b/internal/lkgateway/web/templates/admin_home.html @@ -44,7 +44,7 @@ {{slice .ID 0 8}} {{.Investor.LastName}} {{slice .Investor.FirstName 0 1}}. {{len .Securities}} - {{.Status}} + {{ruState .Status}} открыть → {{end}}