Мастер: 9 шагов — организация (реквизиты из DEPO, логотип) и QUIK МНП общими блоками из настроек; модули видны строго по лицензии; ЕСИА Финанс — отдельный модуль; логотип Фонтвьель только у Фонтвьель; пояснения по сторонним компонентам
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -43,10 +43,7 @@ var templateFuncs = template.FuncMap{
|
||||
"fallbackTpl": fallback,
|
||||
// logoSrc — data-URI логотипа отчётов: загруженный из настроек или встроенный.
|
||||
"logoSrc": func(uri string) template.URL {
|
||||
if strings.TrimSpace(uri) == "" {
|
||||
return template.URL(fontvielleLogoDataURI)
|
||||
}
|
||||
return template.URL(uri)
|
||||
return template.URL(defaultLogoDataURI(uri))
|
||||
},
|
||||
// m2mCode — человекочитаемое название кода M2M: «Текст (M2Mxx)».
|
||||
"m2mCode": m2mCodeLabel,
|
||||
@@ -216,6 +213,7 @@ func newAdmin() (*admin, error) {
|
||||
parse := func(content string) (*template.Template, error) {
|
||||
return template.New("layout.html").Funcs(templateFuncs).ParseFS(templatesFS,
|
||||
"web/templates/layout.html",
|
||||
"web/templates/part_*.html", // общие блоки (например, «Обмен Fancy» для настроек и мастера)
|
||||
"web/templates/"+content)
|
||||
}
|
||||
home, err := parse("admin_home.html")
|
||||
|
||||
@@ -893,13 +893,15 @@ func moduleForPath(p string) string {
|
||||
return "quik-mnp"
|
||||
case has("/admin/setup/quikexport"):
|
||||
return "quik-export"
|
||||
case strings.HasPrefix(p, "/admin/setup/lk/"): // личный кабинет клиента (bj-lk); /admin/setup/lk* без «/» — ЕСИА Финанс, ядро
|
||||
case strings.HasPrefix(p, "/admin/setup/lk/"): // личный кабинет клиента (bj-lk)
|
||||
return "web-cabinet"
|
||||
case p == "/admin/setup/lk", has("/admin/setup/lk-check"), has("/admin/setup/lk-integration"), has("/admin/setup/lk-pull"):
|
||||
return "esia-finance" // интеграция с платформой ЕСИА Финанс — отдельный модуль лицензии
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var moduleTitles = map[string]string{"quik-mnp": "QUIK: неторговые поручения и обмен с DEPO", "quik-export": "Экспорт биржевой информации QUIK", "web-cabinet": "Личный кабинет клиента", "npr": "Риск-монитор НПР / УДС"}
|
||||
var moduleTitles = map[string]string{"esia-finance": "ЕСИА Финанс", "quik-mnp": "QUIK: неторговые поручения и обмен с DEPO", "quik-export": "Экспорт биржевой информации QUIK", "web-cabinet": "Личный кабинет клиента", "npr": "Риск-монитор НПР / УДС"}
|
||||
|
||||
func moduleNotLicensed(w http.ResponseWriter, f string) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
@@ -11,7 +11,32 @@ func orgLogoIMG() string {
|
||||
return `<img src="` + u + `" alt="Логотип организации" style="height:28px;margin-left:auto;flex:none">`
|
||||
}
|
||||
}
|
||||
return fontvielleLogoIMG
|
||||
if builtinLogoAllowed() {
|
||||
return fontvielleLogoIMG
|
||||
}
|
||||
return "" // своего логотипа нет — справа в шапке пусто
|
||||
}
|
||||
|
||||
// builtinLogoAllowed — встроенный логотип Фонтвьель только на сервере Фонтвьель (лицензия выдана
|
||||
// им): у других клиентов на чистой установке чужого логотипа быть не должно.
|
||||
func builtinLogoAllowed() bool {
|
||||
if globalRC == nil {
|
||||
return false
|
||||
}
|
||||
t := strings.ToLower(licenseStatus(globalRC).Tenant)
|
||||
return strings.Contains(t, "фонтвьел") || strings.Contains(t, "fontvielle")
|
||||
}
|
||||
|
||||
// defaultLogoDataURI — логотип для отчётов: загруженный в «Оформлении», иначе встроенный (только у
|
||||
// Фонтвьель), иначе пусто.
|
||||
func defaultLogoDataURI(uploaded string) string {
|
||||
if u := strings.TrimSpace(uploaded); u != "" {
|
||||
return u
|
||||
}
|
||||
if builtinLogoAllowed() {
|
||||
return fontvielleLogoDataURI
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// fontvielle_logo.go — логотип ООО ИК «Фонтвьель» (data-URI PNG), встраивается в
|
||||
|
||||
@@ -42,6 +42,7 @@ var knownFeatures = []struct{ Key, Title string }{
|
||||
{"quik-mnp", "QUIK: модуль неторговых поручений (цикл M2M)"},
|
||||
{"quik-export", "QUIK: экспорт биржевой информации"},
|
||||
{"npr", "Риск-монитор НПР / УДС"},
|
||||
{"esia-finance", "ЕСИА Финанс (платформа личного кабинета)"},
|
||||
}
|
||||
|
||||
func licenseFeatureViews(rc *RuntimeConfig) []LicenseFeatureView {
|
||||
|
||||
@@ -274,6 +274,9 @@ func (s *Server) pollLKClaims(ctx context.Context) {
|
||||
if _, ok := lkActiveEndpoint(s.rc); !ok {
|
||||
continue // ЛК не настроен — тихо ждём
|
||||
}
|
||||
if !moduleAllowed(s.rc, "esia-finance") {
|
||||
continue // ЕСИА Финанс — отдельный модуль лицензии
|
||||
}
|
||||
cctx, cancel := context.WithTimeout(ctx, 50*time.Second)
|
||||
res, err := PullLKClaims(cctx, s.rc, s.svc)
|
||||
cancel()
|
||||
|
||||
@@ -71,13 +71,12 @@ func writeM2MPlanHTML(w http.ResponseWriter, r *http.Request, h *setupHandlers)
|
||||
Date: strings.TrimSpace(r.URL.Query().Get("date")),
|
||||
Generated: time.Now().Format("02.01.2006"),
|
||||
CpName: nonEmpty(plan.CounterpartyName, "Контрагент"), CpCode: plan.CounterpartyCode, CpINN: plan.CounterpartyINN,
|
||||
OurName: nonEmpty(m2mPartyName(ourCode), "Фонтвьель"), OurCode: ourCode, OurINN: nonEmpty(plan.OurINN, m2mINNByCode(ourCode)),
|
||||
OurName: nonEmpty(m2mPartyName(ourCode), orgName(h.rc.Snapshot())), OurCode: ourCode, OurINN: nonEmpty(plan.OurINN, m2mINNByCode(ourCode)),
|
||||
}
|
||||
if d.Date == "" {
|
||||
d.Date = time.Now().AddDate(0, 0, 1).Format("02.01.2006")
|
||||
}
|
||||
d.LogoSrc = template.URL(fontvielleLogoDataURI)
|
||||
if u := strings.TrimSpace(h.rc.Snapshot().Branding.LogoDataURI); strings.HasPrefix(u, "data:image/") {
|
||||
if u := defaultLogoDataURI(h.rc.Snapshot().Branding.LogoDataURI); strings.HasPrefix(u, "data:image/") {
|
||||
d.LogoSrc = template.URL(u)
|
||||
}
|
||||
party := func(k string) string {
|
||||
@@ -290,7 +289,7 @@ select.btn{padding:4px 8px}
|
||||
@media print{body{background:#fff;color:#000}.card{border:none;padding:8px 0;page-break-inside:avoid}.wrap{padding:0}.toolbar,.noprint,.st button:not(.on){display:none}textarea.f,input.f{border:none;padding:0}}
|
||||
</style></head><body><div class="wrap">
|
||||
|
||||
<div class="brand"><img src="{{.LogoSrc}}" alt="{{.OurName}}"><span class="note">{{.OurName}} · код участника {{.OurCode}}</span></div>
|
||||
<div class="brand">{{if .LogoSrc}}<img src="{{.LogoSrc}}" alt="{{.OurName}}">{{end}}<span class="note">{{.OurName}} · код участника {{.OurCode}}</span></div>
|
||||
<h1>План тестирования переводов ценных бумаг через MOEX МОСТ (M2M)</h1>
|
||||
<p class="sub">Тестовый контур НРД · дата проведения: <b>{{.Date}}</b> · документ сформирован {{.Generated}}</p>
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ type WizardData struct {
|
||||
DepoEdition string // редакция SQL Server клиента (пусто — не определена)
|
||||
DepoAgent bool // у SQL Server есть SQL Agent (не Express, не PostgreSQL)
|
||||
DepoJobDef string // способ обработки по умолчанию для мастера
|
||||
Fancy *SetupData // шаг 3: общий блок «Обмен Fancy» (тот же, что в настройках)
|
||||
ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена)
|
||||
CryptoSvc string // состояние службы bj-crypto (systemctl is-active)
|
||||
PGSummary string // куда подключена база bj, без пароля
|
||||
@@ -185,6 +186,8 @@ type WizardData struct {
|
||||
Done struct {
|
||||
License bool
|
||||
Depo bool
|
||||
Org bool // организация (код участника, ИНН) задана
|
||||
Quik bool // QUIK МНП проверен (или модуль не в лицензии)
|
||||
Postgres bool
|
||||
Crypto bool
|
||||
Certs bool
|
||||
@@ -253,17 +256,37 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
|
||||
progress := wizardProgress()
|
||||
step := progress + 1
|
||||
if v := r.URL.Query().Get("step"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 7 {
|
||||
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= wizardSteps {
|
||||
step = n
|
||||
if n-1 > progress {
|
||||
setWizardProgress(n - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if step > 7 {
|
||||
step = 7
|
||||
if step > wizardSteps {
|
||||
step = wizardSteps
|
||||
}
|
||||
d.Step = step
|
||||
if step == 4 && strings.TrimSpace(s.Fancy.Server) != "" && (orgCode(s) == "" || orgINN(s) == "") && depoDetectResult() == nil {
|
||||
// организация ещё не задана, а база DEPO подключена — определяем реквизиты сами: карточка
|
||||
// покажет их заполненными, пользователю остаётся проверить и сохранить
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
if res, err := depoDetectRequisites(ctx, s.Fancy); err == nil {
|
||||
lastDepoDetect.mu.Lock()
|
||||
lastDepoDetect.r = res
|
||||
lastDepoDetect.mu.Unlock()
|
||||
d.Suggest = res
|
||||
} else {
|
||||
log.Printf("wizard: реквизиты из DEPO не определены: %v", err)
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
if step >= 3 && step <= 5 { // DEPO, организация, QUIK МНП — общие блоки из «Настройки»
|
||||
fd := h.setupData(w, r, "")
|
||||
d.Fancy = &fd
|
||||
}
|
||||
d.Done.Org = orgCode(s) != "" && orgINN(s) != ""
|
||||
d.Done.Quik = !d.DepoLicensed || s.Quik.Verified
|
||||
if step == 3 && d.DepoLicensed && strings.TrimSpace(s.Fancy.Server) != "" {
|
||||
// Есть ли у сервера SQL Agent: у Express его нет, у PostgreSQL — тоже; тогда обработку
|
||||
// запускает сам bj. Иначе по умолчанию джобу SQL Agent создаёт bj.
|
||||
@@ -304,6 +327,10 @@ func depoServerEdition(ctx context.Context, f FancyMSSQLSettings) (string, bool)
|
||||
return ed, eng != 4
|
||||
}
|
||||
|
||||
// wizardSteps — шагов в мастере: 1 лицензия, 2 база bj, 3 учёт DEPO, 4 наша организация, 5 QUIK МНП,
|
||||
// 6 Валидата, 7 сертификаты, 8 шлюз НРД, 9 проверка.
|
||||
const wizardSteps = 9
|
||||
|
||||
// wizardProgressFile — номер последнего пройденного шага мастера (переход «Далее» на шаг N
|
||||
// означает, что шаги до N пройдены).
|
||||
const wizardProgressFile = "/var/lib/bj/.bj/wizard-progress"
|
||||
@@ -477,6 +504,11 @@ func HealthLabel(name string) string {
|
||||
}
|
||||
|
||||
func (h *setupHandlers) renderSetup(w http.ResponseWriter, req *http.Request, flash string) {
|
||||
render(w, h.tpl.a.setup, h.setupData(w, req, flash))
|
||||
}
|
||||
|
||||
// setupData — данные страницы «Настройка»; их же получает общий блок «Обмен Fancy» в мастере.
|
||||
func (h *setupHandlers) setupData(w http.ResponseWriter, req *http.Request, flash string) SetupData {
|
||||
s := h.rc.Snapshot()
|
||||
r := h.rc.ReadinessSummary()
|
||||
ready := 0
|
||||
@@ -526,7 +558,7 @@ func (h *setupHandlers) renderSetup(w http.ResponseWriter, req *http.Request, fl
|
||||
if errVal := errMsgFromQuery(_q(w)); errVal != "" {
|
||||
data.Error = errVal
|
||||
}
|
||||
render(w, h.tpl.a.setup, data)
|
||||
return data
|
||||
}
|
||||
|
||||
func (h *setupHandlers) savePostgres(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
<nav class="settings-nav">
|
||||
<button data-sec="overview" class="active"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/></svg></span>Обзор</button>
|
||||
<button data-sec="db"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5.5" rx="7" ry="2.6"/><path d="M5 5.5v13c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6v-13"/><path d="M5 12c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6"/></svg></span>База данных<span class="ind {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span></button>
|
||||
{{if or .AdvancedUnlocked (mod "quik-mnp")}}<button data-sec="fancy"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>Обмен Fancy<span class="ind {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{end}}"></span></button>
|
||||
{{if mod "quik-mnp"}}<button data-sec="fancy"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>Обмен Fancy<span class="ind {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{end}}"></span></button>
|
||||
<button data-sec="quik"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20V4M4 20h16"/><rect x="7" y="12" width="2.6" height="5"/><rect x="12" y="8" width="2.6" height="9"/><rect x="17" y="14" width="2.6" height="3"/></svg></span>QUIK МНП<span class="ind {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{end}}"></span></button>{{end}}
|
||||
{{if or .AdvancedUnlocked (mod "quik-export") (mod "npr")}}<button data-sec="quikexport"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l5-5 4 4 7-8"/><path d="M14 8h6v6"/></svg></span>Экспорт QUIK<span class="ind {{.QuikExport.Ind}}"></span></button>{{end}}
|
||||
{{if or (mod "quik-export") (mod "npr")}}<button data-sec="quikexport"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l5-5 4 4 7-8"/><path d="M14 8h6v6"/></svg></span>Экспорт QUIK<span class="ind {{.QuikExport.Ind}}"></span></button>{{end}}
|
||||
<button data-sec="users"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><circle cx="17" cy="9" r="2.5"/><path d="M15.5 14.5c2.8.3 5.5 2.5 5.5 5.5"/></svg></span>Пользователи<span class="ind {{if .Users.Open}}warn{{else}}ok{{end}}"></span></button>
|
||||
<button data-sec="crypto"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>Криптография<span class="ind {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span></button>
|
||||
<button data-sec="nsd"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M4 21V10M20 21V10M3 10l9-6 9 6M8 21v-7M12 21v-7M16 21v-7"/></svg></span>НРД<span class="ind {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span></button>
|
||||
{{if or .AdvancedUnlocked (mod "web-cabinet")}}<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||||
<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>ЕСИА Финанс<span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>
|
||||
{{if mod "web-cabinet"}}<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||||
{{if mod "esia-finance"}}<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>ЕСИА Финанс<span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||||
<button data-sec="tests"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Диагностика</button>
|
||||
<button data-sec="alerts"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>Оповещения<span class="ind {{if or .Settings.Alerts.SMTP.Enabled .Settings.Alerts.Zabbix.Enabled}}ok{{end}}"></span></button>
|
||||
<button data-sec="update"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V8M6 12l6-6 6 6"/><path d="M5 4h14"/></svg></span>Обновления{{if .Settings.Update.Available}}<span class="ind warn"></span>{{end}}</button>
|
||||
@@ -81,197 +81,18 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{if or .AdvancedUnlocked (mod "quik-mnp")}}
|
||||
{{if mod "quik-mnp"}}
|
||||
{{/* ============ ОБМЕН FANCY (MSSQL) — перспективный модуль ============ */}}
|
||||
<section class="settings-section" id="sec-fancy">
|
||||
<h1>Обмен Fancy (MSSQL / PostgreSQL)</h1>
|
||||
{{template "verline" (.Versions.Get "depo")}}
|
||||
<div class="card" id="sec-fancy-conn">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Подключение к серверу БД клиента</h2>
|
||||
<p class="muted">Сервер учётной базы Fancy DEPO: картотека лиц, счета и договоры, остатки, опции (признак квалифицированного инвестора), трансферные таблицы обмена. Поддерживаются <strong>Microsoft SQL Server</strong> и <strong>PostgreSQL</strong>. База указывается ниже, в карточке «Трансферные таблицы DEPO».</p>
|
||||
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
<label>Тип СУБД
|
||||
<select name="db_type" id="fancy-dbtype" style="width:100%" onchange="var p=document.getElementById('fancy-port');if(!p.value||p.value=='1433'||p.value=='5432'){p.value=this.value=='postgres'?'5432':'1433';}">
|
||||
<option value="mssql" {{if not .Settings.Fancy.IsPostgres}}selected{{end}}>Microsoft SQL Server</option>
|
||||
<option value="postgres" {{if .Settings.Fancy.IsPostgres}}selected{{end}}>PostgreSQL</option>
|
||||
</select></label>
|
||||
<label>Сервер (host или host\instance)
|
||||
<input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="10.10.10.x" style="width:100%" required></label>
|
||||
<label>Порт
|
||||
<input type="number" id="fancy-port" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else}}{{.Settings.Fancy.DefaultPort}}{{end}}" style="width:100%"></label>
|
||||
<label>Пользователь (логин БД)
|
||||
<input type="text" name="user" value="{{.Settings.Fancy.User}}" placeholder="bj_user" style="width:100%" required></label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" placeholder="{{if .Settings.Fancy.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||||
<label>Шифрование канала
|
||||
<select name="encrypt" style="width:100%">
|
||||
<option value="disable" {{if eq .Settings.Fancy.Encrypt "disable"}}selected{{end}}>disable — без TLS (типично для LAN)</option>
|
||||
<option value="true" {{if eq .Settings.Fancy.Encrypt "true"}}selected{{end}}>true — с TLS</option>
|
||||
</select></label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button><button type="submit" formaction="/admin/setup/fancy/test" class="btn">Проверить соединение</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-fancy-depo-tables">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Трансферные таблицы DEPO</h2>
|
||||
<p class="muted">Через эти таблицы bj обменивается с учётной системой DEPO: поручения <code>FTV_M2M</code> и их строки <code>FTV_M2M_LINES</code>, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД <code>FTV_M2M_HANDBOOK</code>. Таблицы создаются в базе DEPO на сервере из подключения выше ({{if .DepoTables.Postgres}}PostgreSQL{{else}}Microsoft SQL Server{{end}}). Создание можно повторять: добавляются только недостающие таблицы, колонки и индексы, данные не меняются.</p>
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<label>База DEPO
|
||||
<input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%"></label>
|
||||
<label>Признак ИИС в названии брокерского договора <span class="muted" style="font-weight:400">(с чего начинается номер договора ИИС в DEPO; несколько — через запятую)</span>
|
||||
<input type="text" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}" placeholder="БРИИС" style="width:100%"></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="auto" {{if eq .Settings.Fancy.DepoTablesMode "auto"}}checked{{end}}> <span>Создаёт bj <span class="muted" style="font-weight:400">— вводите логин с правами на создание таблиц в базе DEPO. Он используется один раз и не сохраняется; bj заодно выдаст права своему логину.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="script" {{if eq .Settings.Fancy.DepoTablesMode "script"}}checked{{end}}> <span>Создаёт администратор СУБД по скрипту <span class="muted" style="font-weight:400">— скачайте скрипт и передайте администратору, после выполнения нажмите «Проверить таблицы».</span></span></label>
|
||||
<div><button type="submit" class="btn">Сохранить</button></div>
|
||||
</form>
|
||||
|
||||
{{if eq .Settings.Fancy.DepoTablesMode "auto"}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:12px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<div><b>Логин с правами на базу {{.DepoTables.DB}}</b><br>
|
||||
<span class="muted" style="font-size:12px">{{if .DepoTables.Postgres}}Владелец базы или суперпользователь.{{else}}Роль db_owner в базе (или db_ddladmin + db_securityadmin).{{end}} Если оставить поля пустыми, bj попробует своим логином {{.DepoTables.User}}.</span></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="{{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="width:100%"></label>
|
||||
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
|
||||
</div>
|
||||
<div><button type="submit" class="btn btn-ok" onclick="return confirm('Создать трансферные таблицы в базе {{.DepoTables.DB}}?')">⚙ Создать таблицы</button></div>
|
||||
</form>
|
||||
{{else if eq .Settings.Fancy.DepoTablesMode "script"}}
|
||||
<div style="margin-top:12px"><b>Скрипт для администратора СУБД</b> — <a href="/admin/setup/fancy/depo-tables/script.sql">скачать {{.DepoTables.ScriptFile}}</a>
|
||||
<pre class="copyable" style="max-height:360px;overflow:auto">{{.DepoTables.Script}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-tables"><button type="submit" class="btn btn-ok">✓ Проверить таблицы</button></form>
|
||||
<span class="muted" style="font-size:12px">логином bj: таблицы на месте, колонки совпадают, есть права на чтение и запись</span>
|
||||
</div>
|
||||
{{if .Settings.Fancy.DepoTablesCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoTablesCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}} <span class="muted">({{.Settings.Fancy.DepoTablesCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-fancy-depo-job">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoJobCheckOK}}ok{{else if .Settings.Fancy.DepoJobMode}}warn{{else}}err{{end}}"></span>Где запускается обработка трансферных таблиц DEPO</h2>
|
||||
<p class="muted">Поручения и события, которые bj пишет в <code>FTV_M2M*</code>, обрабатывает процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO. Выберите, кто её запускает, выполните инструкцию и нажмите «Проверить готовность» — без зелёной отметки обмен с DEPO работать не будет.</p>
|
||||
<form method="post" action="/admin/setup/fancy/depo-job" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-job">
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="bj" {{if eq .Settings.Fancy.DepoJobMode "bj"}}checked{{end}}> <span>Внутри bj <span class="muted" style="font-weight:400">— bj сам вызывает процедуру по расписанию. На сервере клиента нужно одно право на процедуру. Рекомендуется.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-auto" {{if eq .Settings.Fancy.DepoJobMode "sql-auto"}}checked{{end}}> <span>Джоба SQL Agent, создаёт bj <span class="muted" style="font-weight:400">— логину bj выдаётся роль оператора SQL Agent, bj создаёт джобу кнопкой.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-manual" {{if eq .Settings.Fancy.DepoJobMode "sql-manual"}}checked{{end}}> <span>Джоба SQL Agent, настраивает администратор <span class="muted" style="font-weight:400">— передаёте скрипт, bj проверяет, что джоба работает.</span></span></label>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
|
||||
<label>Интервал, секунд <input type="number" name="depo_job_every_sec" min="5" max="3600" value="{{.DepoJob.Every}}" style="width:100%"></label>
|
||||
<label>Процедура <input type="text" name="depo_job_proc" value="{{.Settings.Fancy.DepoJobProc}}" placeholder="dbo.FTV_M2M_JOB" style="width:100%"></label>
|
||||
<label>Работает от имени <input type="text" name="depo_job_run_as" value="{{.Settings.Fancy.DepoJobRunAs}}" placeholder="SYSDBA" style="width:100%"></label>
|
||||
</div>
|
||||
<p class="muted" style="font-size:12px;margin:0">Процедура обработки выполняется от имени пользователя базы DEPO (у разработчика Fancy это <code>SYSDBA</code>). У клиента такого пользователя может не быть или он называется иначе: «Проверить готовность» проверит, что он есть в базе.</p>
|
||||
<div><button type="submit" class="btn">Сохранить способ</button></div>
|
||||
</form>
|
||||
|
||||
{{if eq .Settings.Fancy.DepoJobMode "bj"}}
|
||||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — выполнить один раз:
|
||||
<pre class="copyable">{{.DepoJob.GrantScript}}</pre></div>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "sql-auto"}}
|
||||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — выдать логину bj права оператора SQL Agent (SQL Agent должен быть запущен):
|
||||
<pre class="copyable">{{.DepoJob.AgentRights}}</pre>
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/create" style="margin:8px 0 0"><input type="hidden" name="section" value="sec-fancy-depo-job"><button type="submit" class="btn btn-ok" onclick="return confirm('Создать джобу SQL Agent {{.DepoJob.Name}} на сервере клиента?')">⚙ Создать джобу</button></form></div>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}}
|
||||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — создать джобу (SQL Agent должен быть запущен, служебная учётная запись Agent должна иметь право выполнять процедуру):
|
||||
<pre class="copyable">{{.DepoJob.AgentScript}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
{{if .Settings.Fancy.DepoJobMode}}
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-job"><button type="submit" class="btn btn-ok">✓ Проверить готовность</button></form>
|
||||
<span class="muted" style="font-size:12px">для вариантов с джобой bj пишет служебное событие BJ_PING и ждёт до 40 с, пока обработчик его отметит</span>
|
||||
</div>
|
||||
{{if .Settings.Fancy.DepoJobCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoJobCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}} <span class="muted">({{.Settings.Fancy.DepoJobCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||||
{{if and (eq .Settings.Fancy.DepoJobMode "bj") .Settings.Fancy.DepoJobLast}}<p class="muted" style="font-size:12px;margin-top:6px">Последний вызов процедуры — {{.Settings.Fancy.DepoJobLast}}</p>{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "fancySection" .}}
|
||||
|
||||
</section>
|
||||
|
||||
{{/* ============ QUIK МНП ============ */}}
|
||||
<section class="settings-section" id="sec-quik">
|
||||
<h1>QUIK — Модуль неторговых поручений</h1>
|
||||
{{template "verline" (.Versions.Get "quik-mnp")}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{else}}err{{end}}"></span>Подключение к БД МНП QUIK (MSSQL)</h2>
|
||||
<p class="muted">При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД <strong>боевая</strong> — проверка только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью <code>InstrEu</code>.<br>
|
||||
<a href="/admin/help/quik"><strong>Инструкция для администратора QUIK</strong></a> — что настроить в QuikAdmin для M2M · <a href="/admin/docs/">документы ARQA</a>.</p>
|
||||
<form method="post" action="/admin/setup/quik" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
<label>Сервер (host или host\instance)
|
||||
<input type="text" name="server" value="{{.Settings.Quik.Server}}" placeholder="адрес SQL Server QUIK" style="width:100%" required></label>
|
||||
<label>Порт
|
||||
<input type="number" name="port" value="{{if .Settings.Quik.Port}}{{.Settings.Quik.Port}}{{else}}1433{{end}}" placeholder="8849" style="width:100%"></label>
|
||||
<label>Имя БД (если требуется, иначе оставьте пустым)
|
||||
<input type="text" name="database" value="{{.Settings.Quik.Database}}" placeholder="(по умолчанию)" style="width:100%"></label>
|
||||
<label>Логин (роль InstrEu)
|
||||
<input type="text" name="user" value="{{.Settings.Quik.User}}" placeholder="логин SQL" style="width:100%" required></label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" placeholder="{{if .Settings.Quik.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||||
<label>Шифрование канала
|
||||
<select name="encrypt" style="width:100%">
|
||||
<option value="true" {{if eq .Settings.Quik.Encrypt "true"}}selected{{end}}>true — с TLS (боевой сервер обычно требует)</option>
|
||||
<option value="disable" {{if eq .Settings.Quik.Encrypt "disable"}}selected{{end}}>disable — без TLS</option>
|
||||
</select></label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400">
|
||||
<input type="checkbox" name="trust" value="1" {{if .Settings.Quik.Trust}}checked{{end}}>
|
||||
Доверять сертификату сервера (TrustServerCertificate — для самоподписанного)</label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button type="submit" class="btn btn-ok">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/quik/test" style="margin:0"><button type="submit" class="btn">Проверить подключение (чтение)</button></form>
|
||||
</div>
|
||||
<p class="muted" style="margin-top:12px">Статус: {{if .Settings.Quik.Verified}}<span style="color:var(--ok)">✓ подключение проверено</span>{{else}}<span class="muted">не проверено</span>{{end}}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-quik-cycle">
|
||||
<h2><span class="dot {{if .Settings.Quik.Emulate}}warn{{else if not .Settings.Quik.WritesEnabled}}warn{{else if or .Settings.Quik.AutoOut .Settings.Quik.AutoIn}}ok{{else}}warn{{end}}"></span>Автоматический цикл M2M в МНП{{if .Settings.Quik.Emulate}} <span class="hero-status warn">{{if devOn}}имитация{{else}}остановлен{{end}}</span>{{end}}</h2>
|
||||
<p class="muted">Что делает bj в Модуле неторговых поручений по ходу перевода: <strong>исходящий</strong> — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; <strong>входящий</strong> — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Каждый шаг виден в карточке перевода и на странице <a href="/admin/quik">QUIK МНП</a>.</p>
|
||||
<form method="post" action="/admin/setup/quik/cycle" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||||
<input type="hidden" name="section" value="sec-quik-cycle">
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||||
<input type="checkbox" name="writes_enabled" value="1" {{if .Settings.Quik.WritesEnabled}}checked{{end}}>
|
||||
Разрешить записи в МНП (главный предохранитель — база боевая)</label>
|
||||
{{if devOn}} <label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||||
<input type="checkbox" name="emulate" value="1" {{if .Settings.Quik.Emulate}}checked{{end}}>
|
||||
Имитация: шаги цикла (блокировка, подпись, снятие, зачисление) проходят и отмечаются «имитация», в МНП ничего не пишется — пока нет пользователя QUIK для блокировок и лимитов. Имеет приоритет над записью.</label>{{end}}
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="auto_out" value="1" {{if .Settings.Quik.AutoOut}}checked{{end}}>
|
||||
Исходящие переводы: регистрировать при отправке, подписывать / снимать по ответу автоматически</label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="auto_in" value="1" {{if .Settings.Quik.AutoIn}}checked{{end}}>
|
||||
Входящие переводы: зачислять в QUIK после приёма поручения НРД автоматически</label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="poll_terminal" value="1" {{if .Settings.Quik.PollTerminal}}checked{{end}}>
|
||||
Принимаем поручения M2M из QUIK: клиентам разрешена подача «Вывод ЦБ (M2M)» из терминала / мобильного приложения — bj опрашивает их и создаёт заявку оператору. Если фирма такую подачу не разрешает, оставьте выключенным: раздел «Поручения QUIK» их показывать не будет.</label>
|
||||
<label>Торговый счёт для регистрации поручений
|
||||
<input type="text" name="account" value="{{.Settings.Quik.Account}}" placeholder="L01+00000F00" style="width:100%"></label>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label>МНП-инструмент вывода <span class="muted">(пусто = взять из класса INSTROUTM2M)</span>
|
||||
<input type="text" name="sec_out" value="{{.Settings.Quik.SecOut}}" placeholder="RUROUTM2M" style="width:100%"></label>
|
||||
<label>МНП-инструмент ввода <span class="muted">(пусто = из класса INSTRINM2M)</span>
|
||||
<input type="text" name="sec_in" value="{{.Settings.Quik.SecIn}}" placeholder="RURINM2M" style="width:100%"></label>
|
||||
</div>
|
||||
<label>Исключения кодов клиентов <span class="muted">(по умолчанию «БР-001» → «BR001»; строка «номер портфеля=код QUIK»)</span>
|
||||
<textarea name="client_code_map" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:12px" placeholder="БР-4-4=BR44">{{quikCodeMap .Settings.Quik.ClientCodeMap}}</textarea></label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{if .Settings.Quik.LastReport}}
|
||||
<div class="card">
|
||||
<h2>Полный ответ сервера (последняя проверка)</h2>
|
||||
<p class="muted">Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK.
|
||||
{{if not .Settings.Quik.LastAt.IsZero}}Проверка от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC.{{end}}</p>
|
||||
<div style="display:flex;gap:8px;margin:8px 0">
|
||||
<button type="button" class="btn" onclick="var t=document.getElementById('quik-report').textContent;navigator.clipboard&&navigator.clipboard.writeText(t);this.textContent='Скопировано ✓';setTimeout(()=>this.textContent='Скопировать отчёт',1500)">Скопировать отчёт</button>
|
||||
</div>
|
||||
<pre id="quik-report" style="white-space:pre-wrap;word-break:break-word;background:var(--surface-2,#0d1117);color:var(--code-fg,#c9d1d9);padding:14px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;max-height:420px;user-select:all">{{.Settings.Quik.LastReport}}</pre>
|
||||
</div>
|
||||
{{end}}
|
||||
{{template "quikSection" .}}
|
||||
</section>
|
||||
|
||||
{{end}}{{/* /AdvancedUnlocked — конец перспективных модулей Fancy/QUIK */}}
|
||||
@@ -381,12 +202,12 @@
|
||||
</section>
|
||||
|
||||
{{/* ============ ЭКСПОРТ БИРЖЕВОЙ ИНФОРМАЦИИ (QUIK) ============ */}}
|
||||
{{if or .AdvancedUnlocked (mod "quik-export") (mod "npr")}}
|
||||
{{if or (mod "quik-export") (mod "npr")}}
|
||||
<section class="settings-section" id="sec-quikexport">
|
||||
{{if or .AdvancedUnlocked (mod "quik-export")}}{{.QuikExport.HTML}}{{end}}
|
||||
{{if mod "quik-export"}}{{.QuikExport.HTML}}{{end}}
|
||||
|
||||
{{/* модуль НПР/УДС работает поверх базы экспорта QUIK — его настройки здесь же */}}
|
||||
{{if or .AdvancedUnlocked (mod "npr")}}<div class="card" id="sec-npr">
|
||||
{{if mod "npr"}}<div class="card" id="sec-npr">
|
||||
<h2><span class="dot {{if .Settings.NPR.Enabled}}ok{{else}}warn{{end}}"></span>Модуль «НПР / УДС» (риск-монитор по данным QUIK)</h2>
|
||||
<p class="muted">Показывает НПР1 / НПР2 / УДС клиентов маржинальной торговли по базе экспорта QUIK (схема <code>npr</code>). Отдельно лицензируемый модуль (признак <code>npr</code>); при включении в меню появляется пункт «НПР / УДС». Подключение — логин только для чтения.</p>
|
||||
{{template "verline" (.Versions.Get "bj-npr")}}
|
||||
@@ -489,70 +310,7 @@
|
||||
<section class="settings-section" id="sec-nsd">
|
||||
<h1>НРД</h1>
|
||||
{{template "verline" (.Versions.Get "ish")}}
|
||||
<div class="card" id="sec-org">
|
||||
<h2><span class="dot {{if and .OrgINN .OrgCode}}ok{{else}}err{{end}}"></span>Наша организация</h2>
|
||||
<p class="muted">Депозитарий — участник MOEX МОСТ, от имени которого bj отправляет и принимает переводы: ИНН передающего и зачисляющего депозитария в пакетах, код отправителя и код фирмы в QUIK. Выберите себя из справочника участников НРД или введите данные вручную.</p>
|
||||
{{if and .OrgINN .OrgCode}}<p style="margin:8px 0 0"><b>{{if .OrgName}}{{.OrgName}}{{else}}—{{end}}</b> · ИНН <code>{{.OrgINN}}</code> · код <code>{{.OrgCode}}</code>{{if not .Settings.NSD.OrgINN}} <span class="muted">(ИНН взят из справочника по коду)</span>{{end}}</p>
|
||||
{{else}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
|
||||
<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
|
||||
<input type="hidden" name="section" value="sec-org">
|
||||
<label>Найти себя в справочнике участников M2M</label>
|
||||
<div class="hb-wrap"><input id="orgq" placeholder="Выберите из списка или введите ИНН, название, код" autocomplete="off"><button type="button" class="hb-toggle" title="Весь список">▾</button></div>
|
||||
<div id="orgres" class="hb-res"></div>
|
||||
<style>
|
||||
.hb-box{position:relative}
|
||||
.hb-res{margin-top:4px;max-height:320px;overflow:auto;border-radius:8px;border:1px solid var(--border);background:var(--card-2);padding:4px}
|
||||
.hb-res:empty{display:none}
|
||||
.hb-wrap{display:flex;gap:6px}.hb-wrap input{flex:1}
|
||||
.hb-toggle{padding:0 12px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}
|
||||
.hb-hit{display:block;width:100%;text-align:left;padding:7px 10px;margin:2px 0;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;font-size:13px}
|
||||
.hb-hit:hover{border-color:var(--accent)}
|
||||
.hb-m{color:var(--muted);font-size:12px}
|
||||
</style>
|
||||
<script>
|
||||
// hbFind — подбор участника справочника НРД по ИНН, названию или коду (без учёта регистра и кавычек).
|
||||
function hbNorm(s){return String(s||'').toLowerCase().replace(/ё/g,'е').replace(/[«»"'“”.,()]/g,' ').replace(/\s+/g,' ').trim()}
|
||||
function hbFind(list,q){q=hbNorm(q);
|
||||
if(!q)return list.slice().sort((a,b)=>(a.kind===b.kind?0:a.kind==='depo'?-1:1)||hbNorm(a.name).localeCompare(hbNorm(b.name),'ru'));
|
||||
const words=q.split(' ');
|
||||
return list.map(p=>{const hay=hbNorm([p.inn,p.code,p.name,p.full_name,p.display_name,p.eng_name].join(' '));
|
||||
if(!words.every(w=>hay.includes(w)))return null;
|
||||
const score=(String(p.inn).startsWith(q)||String(p.code).toLowerCase().startsWith(q)?0:hbNorm(p.name).startsWith(q)||hbNorm(p.display_name).startsWith(q)?1:2)+(p.kind==='depo'?0:0.5);
|
||||
return {p,score}}).filter(Boolean).sort((a,b)=>a.score-b.score||hbNorm(a.p.name).localeCompare(hbNorm(b.p.name),'ru')).map(x=>x.p)}
|
||||
function hbBind(inp,box,list,onPick){
|
||||
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
||||
let hits=[],picked=inp.dataset.picked==='1';
|
||||
const q=()=>picked?'':inp.value;
|
||||
const draw=()=>{hits=hbFind(list,q());box.innerHTML=(hits.length?'<div class="hb-m" style="padding:2px 4px">'+(q().trim()?'Найдено: ':'Все участники: ')+hits.length+'</div>'+hits.map((p,i)=>'<button type="button" class="hb-hit" data-i="'+i+'" data-inn="'+esc(p.inn)+'" data-code="'+esc(p.code)+'"><b>'+esc(p.name)+'</b> <span class="hb-m">ИНН '+esc(p.inn)+' · '+esc(p.code)+(p.kind==='broker'?' · брокер':'')+'</span></button>').join(''):'<div class="hb-m" style="padding:6px 2px">В справочнике не найдено — введите ИНН и наименование в поля ниже.</div>');box.classList.add('open')};
|
||||
const close=()=>{box.innerHTML='';box.classList.remove('open')};
|
||||
inp.addEventListener('focus',()=>{if(picked)inp.select();draw()});
|
||||
inp.addEventListener('input',()=>{picked=false;draw()});
|
||||
// Enter выбирает только при единственном совпадении: иначе молча ушёл бы первый попавшийся.
|
||||
inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();if(hits.length===1)pick(hits[0]);else if(hits.length>1){const m=box.querySelector('.hb-m');if(m)m.textContent='Найдено: '+hits.length+' — уточните запрос или выберите мышью'}}if(e.key==='Escape')close()});
|
||||
const pick=p=>{onPick(p);picked=true;close()};
|
||||
box.addEventListener('mousedown',e=>e.preventDefault());
|
||||
// Участник — по ИНН и коду с самой кнопки, а не по номеру строки: список мог перерисоваться между показом и нажатием.
|
||||
box.addEventListener('click',e=>{const b=e.target.closest('.hb-hit');if(!b)return;const p=list.find(x=>String(x.inn)===b.dataset.inn&&String(x.code)===b.dataset.code)||hits[+b.dataset.i];if(p)pick(p)});
|
||||
inp.addEventListener('blur',()=>setTimeout(close,150));
|
||||
const tg=inp.parentNode.querySelector('.hb-toggle');
|
||||
if(tg)tg.addEventListener('click',()=>{if(box.classList.contains('open'))close();else{inp.focus();draw()}});
|
||||
}
|
||||
{
|
||||
const inp=document.getElementById('orgq'),f=inp.form;
|
||||
hbBind(inp,document.getElementById('orgres'),{{.Participants}}||[],p=>{f.org_code.value=p.code;f.org_inn.value=p.inn;f.org_name.value=p.full_name||p.name;inp.value=p.name+' · ИНН '+p.inn})
|
||||
}
|
||||
</script>
|
||||
{{if not .Participants}}<span class="muted" style="font-size:12px">Справочник участников ещё не получен — запросите его в «Диагностике» или введите данные вручную.</span>{{end}}
|
||||
<label>Код участника</label>
|
||||
<input type="text" name="org_code" value="{{if .Settings.NSD.OrgCode}}{{.Settings.NSD.OrgCode}}{{else if .Suggest}}{{.Suggest.OrgCode}}{{end}}" placeholder="MC0000000000" maxlength="12">
|
||||
<label>ИНН</label>
|
||||
<input type="text" name="org_inn" value="{{if .Settings.NSD.OrgINN}}{{.Settings.NSD.OrgINN}}{{else if .Suggest}}{{.Suggest.OrgINN}}{{end}}" maxlength="10" inputmode="numeric">
|
||||
<label>Наименование</label>
|
||||
<input type="text" name="org_name" value="{{if .Settings.NSD.OrgName}}{{.Settings.NSD.OrgName}}{{else if .Suggest}}{{.Suggest.OrgName}}{{end}}">
|
||||
{{if .Suggest}}{{if .Suggest.OrgName}}<span class="muted" style="font-size:12px">Из DEPO: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}} — подставлено в пустые поля.</span>{{end}}{{end}}
|
||||
<div><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||||
</form>
|
||||
</div>
|
||||
{{template "orgSection" .}}
|
||||
<div class="card" id="m2m-auto">
|
||||
<h2><span class="dot {{if .Settings.M2MAuto.Enabled}}ok{{else}}warn{{end}}"></span>Автоматический режим M2M</h2>
|
||||
<p class="muted">Без участия оператора, чтобы соблюдать сроки: <strong>ответ на входящие переводы</strong> по свежей сверке с DEPO (пакет принимается только целиком; бумага для квалифицированных инвесторов при неполной или непрочитанной квалификации клиента — отказ M2M08), <strong>подача поручения 16/1</strong>, как только НРД пришлёт черновик после нашего согласия, и <strong>подача поручения 16</strong> по исходящим после согласия контрагента. Если DEPO или ИШ недоступны — bj повторяет каждые 3 минуты и шлёт оповещение; без сверки ничего не принимается. Обрабатывается только то, что пришло не раньше чем за сутки до включения. Кнопки в карточках остаются для ручной работы.</p>
|
||||
@@ -625,7 +383,7 @@ function hbBind(inp,box,list,onPick){
|
||||
|
||||
{{/* ============ ЛИЧНЫЙ КАБИНЕТ ============ */}}
|
||||
{{/* ============ КАБИНЕТ КЛИЕНТА (наш модуль bj-lk) ============ */}}
|
||||
{{if or .AdvancedUnlocked (mod "web-cabinet")}}
|
||||
{{if mod "web-cabinet"}}
|
||||
<section class="settings-section" id="sec-cabinet">
|
||||
<h1>Кабинет клиента</h1>
|
||||
<p class="muted" style="margin:-8px 0 16px">Наш личный кабинет для клиентов депозитария: модуль <code>bj-lk</code> на своём сервере или PHP-хостинге, связь с ядром, учётные записи клиентов и сотрудников. Интеграция с платформой ЕСИА Финанс — отдельный раздел.</p>
|
||||
@@ -796,6 +554,7 @@ function hbBind(inp,box,list,onPick){
|
||||
{{end}}
|
||||
|
||||
{{/* ============ ЕСИА ФИНАНС (внешняя платформа ЛК) ============ */}}
|
||||
{{if mod "esia-finance"}}
|
||||
<section class="settings-section" id="sec-lk">
|
||||
<h1>ЕСИА Финанс</h1>
|
||||
<p class="muted" style="margin:-8px 0 16px">Интеграция с внешней платформой личного кабинета <strong>ЕСИА Финанс</strong>: адрес для клиента, приём заявок M2M, возврат статуса и подключение к API (тест/бой). Наш собственный кабинет настраивается в разделе «Кабинет клиента».</p>
|
||||
@@ -924,6 +683,7 @@ function hbBind(inp,box,list,onPick){
|
||||
<p class="muted" style="font-size:12px;margin-top:10px">Итог (сколько получено / отправлено, ошибки) появится вверху страницы.</p>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{/* ============ ДИАГНОСТИКА (id sec-tests сохранён для ссылок) ============ */}}
|
||||
<section class="settings-section" id="sec-tests">
|
||||
@@ -1208,10 +968,10 @@ function hbBind(inp,box,list,onPick){
|
||||
<div style="display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:14px 0;padding:14px;background:var(--card-2);border-radius:12px">
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;margin-bottom:6px">Сейчас в отчётах:</div>
|
||||
<img src="{{logoSrc .Settings.Branding.LogoDataURI}}" alt="логотип" style="height:40px;max-width:280px;background:#fff;padding:6px 10px;border-radius:8px;border:1px solid var(--border)">
|
||||
{{if logoSrc .Settings.Branding.LogoDataURI}}<img src="{{logoSrc .Settings.Branding.LogoDataURI}}" alt="логотип" style="height:40px;max-width:280px;background:#fff;padding:6px 10px;border-radius:8px;border:1px solid var(--border)">{{else}}<span class="muted">логотип не загружен</span>{{end}}
|
||||
</div>
|
||||
<div class="muted" style="font-size:12px">
|
||||
{{if .Settings.Branding.LogoDataURI}}Загружен свой логотип{{if .Settings.Branding.LogoName}}: <b>{{.Settings.Branding.LogoName}}</b>{{end}}.{{else}}Используется стандартный (встроенный) логотип.{{end}}
|
||||
{{if .Settings.Branding.LogoDataURI}}Загружен свой логотип{{if .Settings.Branding.LogoName}}: <b>{{.Settings.Branding.LogoName}}</b>{{end}}.{{else}}Свой логотип не загружен — в шапке отчётов справа пусто.{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1222,8 +982,8 @@ function hbBind(inp,box,list,onPick){
|
||||
<p class="muted" style="font-size:12px;margin-top:8px">Форматы: PNG, JPG, GIF, WEBP, SVG. Максимум 512 КБ. Рекомендуется горизонтальный логотип (высота в шапке — 28px).</p>
|
||||
|
||||
{{if .Settings.Branding.LogoDataURI}}
|
||||
<form method="post" action="/admin/setup/logo/reset" style="margin-top:8px" onsubmit="return confirm('Сбросить логотип на стандартный (Фонтвьель)?');">
|
||||
<button type="submit" class="btn">Сбросить на стандартный</button>
|
||||
<form method="post" action="/admin/setup/logo/reset" style="margin-top:8px" onsubmit="return confirm('Удалить загруженный логотип?');">
|
||||
<button type="submit" class="btn">Удалить логотип</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -1293,4 +1053,3 @@ function hbBind(inp,box,list,onPick){
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
{{define "verline"}}{{if .Title}}<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">{{.Title}}: {{if .Version}}<strong>{{.Version}}</strong>{{else}}не определена{{end}}{{with .Detail}} · {{.}}{{end}}{{with .Err}} · <span style="color:var(--err)">{{.}}</span>{{end}}</p>{{end}}{{end}}
|
||||
|
||||
@@ -32,10 +32,12 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
<a href="/admin/wizard?step=1" title="Перейти к шагу 1" class="wstep {{if .Done.License}}done{{end}} {{if eq .Step 1}}current{{end}}"><span class="bub">{{if .Done.License}}✓{{else}}1{{end}}</span><span class="lbl">Лицензия</span></a>
|
||||
<a href="/admin/wizard?step=2" title="Перейти к шагу 2" class="wstep {{if .Done.Postgres}}done{{end}} {{if eq .Step 2}}current{{end}}"><span class="bub">{{if .Done.Postgres}}✓{{else}}2{{end}}</span><span class="lbl">База bj</span></a>
|
||||
<a href="/admin/wizard?step=3" title="Перейти к шагу 3" class="wstep {{if .Done.Depo}}done{{end}} {{if eq .Step 3}}current{{end}}"><span class="bub">{{if .Done.Depo}}✓{{else}}3{{end}}</span><span class="lbl">Учёт DEPO</span></a>
|
||||
<a href="/admin/wizard?step=4" title="Перейти к шагу 4" class="wstep {{if .Done.Crypto}}done{{end}} {{if eq .Step 4}}current{{end}}"><span class="bub">{{if .Done.Crypto}}✓{{else}}4{{end}}</span><span class="lbl">Валидата</span></a>
|
||||
<a href="/admin/wizard?step=5" title="Перейти к шагу 5" class="wstep {{if .Done.Certs}}done{{end}} {{if eq .Step 5}}current{{end}}"><span class="bub">{{if .Done.Certs}}✓{{else}}5{{end}}</span><span class="lbl">Сертификаты</span></a>
|
||||
<a href="/admin/wizard?step=6" title="Перейти к шагу 6" class="wstep {{if .Done.NSD}}done{{end}} {{if eq .Step 6}}current{{end}}"><span class="bub">{{if .Done.NSD}}✓{{else}}6{{end}}</span><span class="lbl">Шлюз НРД</span></a>
|
||||
<a href="/admin/wizard?step=7" title="Перейти к шагу 7" class="wstep {{if .Done.TestRun}}done{{end}} {{if eq .Step 7}}current{{end}}"><span class="bub">{{if .Done.TestRun}}✓{{else}}7{{end}}</span><span class="lbl">Проверка</span></a>
|
||||
<a href="/admin/wizard?step=4" title="Перейти к шагу 4" class="wstep {{if .Done.Org}}done{{end}} {{if eq .Step 4}}current{{end}}"><span class="bub">{{if .Done.Org}}✓{{else}}4{{end}}</span><span class="lbl">Организация</span></a>
|
||||
<a href="/admin/wizard?step=5" title="Перейти к шагу 5" class="wstep {{if .Done.Quik}}done{{end}} {{if eq .Step 5}}current{{end}}"><span class="bub">{{if .Done.Quik}}✓{{else}}5{{end}}</span><span class="lbl">QUIK МНП</span></a>
|
||||
<a href="/admin/wizard?step=6" title="Перейти к шагу 6" class="wstep {{if .Done.Crypto}}done{{end}} {{if eq .Step 6}}current{{end}}"><span class="bub">{{if .Done.Crypto}}✓{{else}}6{{end}}</span><span class="lbl">Валидата</span></a>
|
||||
<a href="/admin/wizard?step=7" title="Перейти к шагу 7" class="wstep {{if .Done.Certs}}done{{end}} {{if eq .Step 7}}current{{end}}"><span class="bub">{{if .Done.Certs}}✓{{else}}7{{end}}</span><span class="lbl">Сертификаты</span></a>
|
||||
<a href="/admin/wizard?step=8" title="Перейти к шагу 8" class="wstep {{if .Done.NSD}}done{{end}} {{if eq .Step 8}}current{{end}}"><span class="bub">{{if .Done.NSD}}✓{{else}}8{{end}}</span><span class="lbl">Шлюз НРД</span></a>
|
||||
<a href="/admin/wizard?step=9" title="Перейти к шагу 9" class="wstep {{if .Done.TestRun}}done{{end}} {{if eq .Step 9}}current{{end}}"><span class="bub">{{if .Done.TestRun}}✓{{else}}9{{end}}</span><span class="lbl">Проверка</span></a>
|
||||
</div>
|
||||
<p style="text-align:center;margin:-14px 0 18px;font-size:13px"><a href="/admin/">Пропустить мастер и перейти в систему →</a> <span class="muted">— вернуться к нему можно из «Настройка → Мастер настройки»</span></p>
|
||||
|
||||
@@ -88,83 +90,14 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
<h2><span class="dot {{if .Done.Depo}}ok{{else}}err{{end}}"></span>Шаг 3. Учётная система DEPO (Fancy)</h2>
|
||||
<div class="help-block"><strong>Что это.</strong> Подключение к базе депозитарного учёта DEPO (MS SQL / PostgreSQL).<br>
|
||||
<strong>Зачем.</strong> bj сверяет с DEPO каждый перевод (клиент, счёт, остаток, квалификация) и передаёт в DEPO поручения и их исполнение — документы создаются в учёте автоматически. Отсюда же bj возьмёт ваши реквизиты в НРД (шаг 6), чтобы не вводить их вручную.<br>
|
||||
<strong>Что нужно.</strong> Адрес сервера БД и логин с правами чтения DEPO и записи в трансферные таблицы FTV_M2M (выдаёт администратор СУБД).</div>
|
||||
<strong>К кому обратиться.</strong> Адрес сервера базы DEPO, логин и пароль даёт ваш администратор баз данных (тот, кто обслуживает сервер учёта Fancy). Процедуру обработки обмена в базе DEPO ставит разработчик вашей системы учёта Fancy — если её ещё нет, bj подскажет на проверке.<br>
|
||||
<strong>Что ввести здесь.</strong> Адрес сервера, логин и пароль. Всё остальное — таблицы обмена, джобу обработки, реквизиты вашей организации — bj подготовит сам; для таблиц и джобы понадобится разово логин администратора SQL, он не сохраняется.</div>
|
||||
{{if not .DepoLicensed}}
|
||||
<p class="muted">Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается. После добавления модуля в лицензию он станет доступен здесь и в «Настройка → Обмен Fancy».</p>
|
||||
{{else}}
|
||||
<p class="muted" style="margin:6px 0 0">Три части: подключение к базе, трансферные таблицы обмена, обработка (job), которая превращает записи bj в документы DEPO.</p>
|
||||
|
||||
<h3 style="margin-top:16px">1. Подключение к базе DEPO {{if .Settings.Fancy.Server}}<span style="color:var(--ok);font-size:14px">✓ {{.Settings.Fancy.Server}}</span>{{end}}</h3>
|
||||
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
|
||||
<div><label>Тип СУБД</label><select name="db_type" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"><option value="mssql" {{if ne .Settings.Fancy.DBType "postgres"}}selected{{end}}>MS SQL Server</option><option value="postgres" {{if eq .Settings.Fancy.DBType "postgres"}}selected{{end}}>PostgreSQL</option></select></div>
|
||||
<div><label>Сервер (адрес или host\instance)</label><input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="10.0.0.21" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
|
||||
<div><label>Порт</label><input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else}}1433{{end}}" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
|
||||
<div><label>Логин БД</label><input type="text" name="user" value="{{.Settings.Fancy.User}}" autocomplete="off" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
|
||||
<div><label>Пароль {{if .Settings.Fancy.Server}}<span class="muted">(пусто — оставить прежний)</span>{{end}}</label><input type="password" name="password" autocomplete="new-password" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
|
||||
<input type="hidden" name="encrypt" value="{{if .Settings.Fancy.Encrypt}}{{.Settings.Fancy.Encrypt}}{{else}}disable{{end}}">
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap"><button type="submit" class="btn">Сохранить</button>
|
||||
{{if .Settings.Fancy.Server}}<button type="submit" class="btn" formaction="/admin/setup/fancy/test" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Проверить соединение</button>{{end}}</div>
|
||||
</form>
|
||||
{{if .Settings.Fancy.Server}}
|
||||
{{$btn2 := "background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)"}}
|
||||
<h3 style="margin-top:22px">2. Трансферные таблицы FTV_M2M {{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok);font-size:14px">✓ на месте</span>{{end}}</h3>
|
||||
<div class="help-block" style="font-size:13px"><strong>Что это.</strong> Четыре таблицы в базе DEPO, через которые идёт обмен: поручения <code>FTV_M2M</code> и их строки, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД. Данные учёта они не трогают; создание можно повторять — добавляется только недостающее.</div>
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
|
||||
<input type="hidden" name="depo_tables_mode" value="auto">
|
||||
<input type="hidden" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}">
|
||||
<label style="flex:1;min-width:220px">База DEPO на сервере <input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%;padding:8px"></label>
|
||||
<button type="submit" class="btn" style="{{$btn2}}">Сохранить имя базы</button>
|
||||
</form>
|
||||
{{if not .Settings.Fancy.DepoTablesCheckOK}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:10px;display:grid;gap:8px">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
|
||||
<p style="margin:0"><b>Создать таблицы в базе {{.DepoTables.DB}}.</b> <span class="muted" style="font-size:13px">Логин администратора СУБД ({{if .DepoTables.Postgres}}владелец базы{{else}}db_owner в базе или sa{{end}}) нужен один раз и не сохраняется; bj заодно выдаст права своему логину {{.DepoTables.User}}. Пусто — попробовать логином bj.</span></p>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
|
||||
<input type="text" name="admin_user" autocomplete="off" placeholder="логин, например {{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="padding:8px">
|
||||
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px">
|
||||
</div>
|
||||
<div><button type="submit" class="btn">Создать таблицы</button> <a href="/admin/setup/fancy/depo-tables/script.sql" style="font-size:13px;margin-left:8px">или скачать скрипт для администратора</a></div>
|
||||
</form>
|
||||
{{end}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/check" style="margin-top:8px"><input type="hidden" name="return_to" value="/admin/wizard?step=3"><button type="submit" class="btn" style="{{$btn2}}">Проверить таблицы</button></form>
|
||||
{{if .Settings.Fancy.DepoTablesCheck}}<p style="font-size:13px;margin-top:6px;{{if .Settings.Fancy.DepoTablesCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}}</p>{{end}}
|
||||
|
||||
<h3 style="margin-top:22px">3. Обработка обмена (job) {{if .Settings.Fancy.DepoJobCheckOK}}<span style="color:var(--ok);font-size:14px">✓ работает</span>{{end}}</h3>
|
||||
<div class="help-block" style="font-size:13px"><strong>Что это.</strong> Процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO каждые {{.DepoJob.Every}} с забирает записи bj из трансферных таблиц и создаёт по ним документы и операции DEPO. Кто-то должен её запускать.<br>
|
||||
{{if .DepoEdition}}<strong>Ваш сервер:</strong> {{.DepoEdition}} — {{if .DepoAgent}}есть SQL Server Agent, рекомендуем джобу Agent, её создаст bj.{{else}}редакция без SQL Server Agent, процедуру будет запускать сам bj.{{end}}{{else if .DepoTables.Postgres}}<strong>Ваш сервер:</strong> PostgreSQL — процедуру будет запускать сам bj.{{end}}</div>
|
||||
{{$mode := .Settings.Fancy.DepoJobMode}}{{if not $mode}}{{$mode = .DepoJobDef}}{{end}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-job" style="display:grid;gap:6px">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
|
||||
<input type="hidden" name="depo_job_every_sec" value="{{.DepoJob.Every}}">
|
||||
<input type="hidden" name="depo_job_proc" value="{{.Settings.Fancy.DepoJobProc}}">
|
||||
<input type="hidden" name="depo_job_run_as" value="{{.Settings.Fancy.DepoJobRunAs}}">
|
||||
{{if not .DepoTables.Postgres}}<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="sql-auto" {{if eq $mode "sql-auto"}}checked{{end}}> <span><b>Джоба SQL Server Agent, создаёт bj</b> <span class="muted" style="font-size:13px">— рекомендуется: работает, даже когда bj перезапускается; нужен разовый логин администратора SQL.</span></span></label>{{end}}
|
||||
<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="bj" {{if eq $mode "bj"}}checked{{end}}> <span><b>Запускает сам bj</b> <span class="muted" style="font-size:13px">— для SQL Server Express и PostgreSQL; нужно одно право на процедуру.</span></span></label>
|
||||
{{if not .DepoTables.Postgres}}<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="sql-manual" {{if eq $mode "sql-manual"}}checked{{end}}> <span><b>Джоба SQL Server Agent, создаёт администратор</b> <span class="muted" style="font-size:13px">— bj даст готовый скрипт.</span></span></label>{{end}}
|
||||
<div><button type="submit" class="btn" style="{{$btn2}}">{{if .Settings.Fancy.DepoJobMode}}Сменить способ{{else}}Выбрать способ{{end}}</button></div>
|
||||
</form>
|
||||
{{if eq .Settings.Fancy.DepoJobMode "sql-auto"}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/create" style="margin-top:10px;display:grid;gap:8px">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
|
||||
<p style="margin:0"><b>Создать джобу {{.DepoJob.Name}}.</b> <span class="muted" style="font-size:13px">Логин администратора SQL (sysadmin, например sa) нужен один раз и не сохраняется. SQL Server Agent должен быть запущен.</span></p>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
|
||||
<input type="text" name="admin_user" autocomplete="off" placeholder="логин, например sa" style="padding:8px">
|
||||
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px">
|
||||
</div>
|
||||
<div><button type="submit" class="btn">Создать джобу и проверить</button></div>
|
||||
</form>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "bj"}}
|
||||
<p style="margin:10px 0 4px"><b>Администратору SQL — выполнить один раз:</b></p><pre class="copyable" style="font-size:12px">{{.DepoJob.GrantScript}}</pre>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}}
|
||||
<p style="margin:10px 0 4px"><b>Скрипт для администратора SQL:</b></p><pre class="copyable" style="font-size:12px;max-height:260px;overflow:auto">{{.DepoJob.AgentScript}}</pre>
|
||||
{{end}}
|
||||
{{if .Settings.Fancy.DepoJobMode}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/check" style="margin-top:8px"><input type="hidden" name="return_to" value="/admin/wizard?step=3"><button type="submit" class="btn" style="{{$btn2}}">Проверить обработку</button> <span class="muted" style="font-size:12px">bj кладёт служебное событие и ждёт до 40 с, пока его обработают</span></form>
|
||||
{{if .Settings.Fancy.DepoJobCheck}}<p style="font-size:13px;margin-top:6px;{{if .Settings.Fancy.DepoJobCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}}</p>{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
<p class="muted" style="margin:6px 0 0">Это тот же раздел, что «Настройка → Обмен Fancy»: подключение к базе, трансферные таблицы и обработка (job). Что настроите здесь — сразу там, и наоборот.</p>
|
||||
{{if .DepoEdition}}<p style="font-size:13px;margin:8px 0 0"><strong>Ваш SQL Server:</strong> {{.DepoEdition}} — {{if .DepoAgent}}есть SQL Server Agent: рекомендуем джобу Agent, её создаст bj (способ по умолчанию).{{else}}без SQL Server Agent: обработку будет запускать сам bj.{{end}}</p>{{end}}
|
||||
{{with .Fancy}}<div style="margin-top:12px">{{template "fancySection" .}}</div>{{end}}
|
||||
{{end}}
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=2" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 2</a>
|
||||
@@ -211,10 +144,51 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 2: Крипто (Валидата) ============= */}}
|
||||
{{/* ============= ШАГ 4: Наша организация ============= */}}
|
||||
{{if eq .Step 4}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>Шаг 4. СКЗИ «Валидата Клиент L»</h2>
|
||||
<h2><span class="dot {{if .Done.Org}}ok{{else}}err{{end}}"></span>Шаг 4. Наша организация</h2>
|
||||
<div class="help-block"><strong>Что это.</strong> Депозитарий — участник MOEX МОСТ, от имени которого bj отправляет и принимает переводы: код участника, ИНН и наименование попадают в пакеты НРД и в поручения QUIK.<br>
|
||||
<strong>Откуда взять.</strong> Если на шаге 3 подключена база DEPO, bj сам определяет реквизиты по учёту — проверьте их и сохраните. Иначе выберите себя из справочника участников НРД или введите вручную.</div>
|
||||
{{if .Suggest}}{{if .Suggest.OrgName}}{{if not .Done.Org}}<p style="margin:8px 0;padding:8px 12px;border-radius:8px;background:var(--accent-weak)">✓ <strong>Определено из DEPO</strong>: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}}{{if .Suggest.AccountID}}, счёт НРД {{.Suggest.AccountID}}{{end}}. Поля ниже заполнены — проверьте и нажмите «Сохранить».</p>{{end}}{{end}}{{end}}
|
||||
{{with .Fancy}}{{template "orgSection" .}}{{end}}
|
||||
<div class="card" style="margin-top:12px">
|
||||
<h3 style="margin:0 0 6px">Логотип организации (необязательно)</h3>
|
||||
<p class="muted" style="margin:0 0 10px;font-size:13px">Показывается справа в шапке отчётов для регулятора. Можно загрузить сейчас или позже в «Настройка → Оформление».</p>
|
||||
{{if .Settings.Branding.LogoDataURI}}<p style="margin:0 0 8px"><img src="{{.Settings.Branding.LogoDataURI}}" alt="логотип" style="height:36px;background:#fff;padding:4px 8px;border-radius:6px;border:1px solid var(--border)"></p>{{end}}
|
||||
<form method="post" action="/admin/setup/logo" enctype="multipart/form-data" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=4">
|
||||
<input type="file" name="logo" accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,.png,.jpg,.jpeg,.gif,.webp,.svg" required>
|
||||
<button type="submit" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">{{if .Settings.Branding.LogoDataURI}}Заменить логотип{{else}}Загрузить логотип{{end}}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=3" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 3</a>
|
||||
<a href="/admin/wizard?step=5" class="btn" style="{{if not .Done.Org}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Org}}К шагу 5 →{{else}}Пропустить →{{end}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 5: QUIK МНП ============= */}}
|
||||
{{if eq .Step 5}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.Quik}}ok{{else}}err{{end}}"></span>Шаг 5. QUIK — модуль неторговых поручений</h2>
|
||||
<div class="help-block"><strong>Что это.</strong> Модуль неторговых поручений QUIK (МНП) — продукт компании ARQA Technologies, разработчика QUIK. Через него bj блокирует бумаги клиента в QUIK на время перевода, снимает блокировку при отказе и после исполнения проводит поручение.<br>
|
||||
<strong>Если МНП у вас ещё нет.</strong> Обратитесь в ARQA Technologies (<a href="https://arqatech.com" target="_blank" rel="noopener">arqatech.com</a>, служба поддержки QUIK) и попросите подключить модуль неторговых поручений для работы с внешней системой. После подключения ARQA выдаст адрес сервера МНП и учётные данные — их и нужно ввести ниже.<br>
|
||||
<strong>Если МНП уже есть.</strong> Адрес сервера и учётные данные — у вашего администратора QUIK.</div>
|
||||
{{if not .DepoLicensed}}<p class="muted">Модуль неторговых поручений не входит в текущую лицензию — шаг пропускается.</p>
|
||||
{{else}}{{with .Fancy}}{{template "quikSection" .}}{{end}}{{end}}
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=4" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 4</a>
|
||||
<a href="/admin/wizard?step=6" class="btn" style="{{if not .Done.Quik}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Quik}}К шагу 6 →{{else}}Пропустить →{{end}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 2: Крипто (Валидата) ============= */}}
|
||||
{{if eq .Step 6}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>Шаг 6. СКЗИ «Валидата Клиент L»</h2>
|
||||
<p>СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (шаг 5).</p>
|
||||
{{if .ValidataVer}}
|
||||
<p>✓ Валидата установлена вместе с bj: версия {{.ValidataVer}}, каталог <code>/opt/Validata/VDCSP</code>. Ничего настраивать на этом шаге не нужно.</p>
|
||||
@@ -231,20 +205,21 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
{{end}}
|
||||
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=3" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 3</a>
|
||||
{{if .Done.Crypto}}<a href="/admin/wizard?step=5" class="btn" style="text-decoration:none">К шагу 5 →</a>{{else}}<a href="/admin/wizard?step=5&skip=crypto" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить →</a>{{end}}
|
||||
<a href="/admin/wizard?step=5" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 5</a>
|
||||
{{if .Done.Crypto}}<a href="/admin/wizard?step=7" class="btn" style="text-decoration:none">К шагу 7 →</a>{{else}}<a href="/admin/wizard?step=7&skip=crypto" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить →</a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 3: Сертификаты ============= */}}
|
||||
{{if eq .Step 5}}
|
||||
{{if eq .Step 7}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.Certs}}ok{{else}}err{{end}}"></span>Шаг 5. Сертификаты</h2>
|
||||
<h2><span class="dot {{if .Done.Certs}}ok{{else}}err{{end}}"></span>Шаг 7. Сертификаты</h2>
|
||||
<p>Здесь две части: ключ и сертификат вашей организации (ставите вы) и сертификаты НРД и удостоверяющих центров (bj ведёт их сам).</p>
|
||||
|
||||
<h3 style="margin-top:14px">1. Ключ и сертификат организации</h3>
|
||||
<div class="help-block"><strong>Что это.</strong> Ключ ЭП, которым подписываются сообщения в НРД, и его сертификат (выдаёт УЦ Московской биржи). Архив ключа с паролем вам передаёт НРД/УЦ.<br>
|
||||
<div class="help-block"><strong>Что это.</strong> Ключ электронной подписи вашей организации для обмена с НРД и его сертификат.<br>
|
||||
<strong>К кому обратиться.</strong> Ключ выдаёт Удостоверяющий центр Московской биржи; оформляется по заявке в рамках подключения к электронному документообороту НРД. Вам передадут архив ключа и пароль к нему — его и загружают в мастере установки ключа.<br>
|
||||
<strong>Как поставить.</strong> Мастер установки ключа: загрузить архив → запись на носитель → справочник сертификатов → проверка подписи Валидатой.</div>
|
||||
<a href="/admin/setup/keywizard" class="btn" style="text-decoration:none;display:inline-block">Открыть мастер установки ключа →</a>
|
||||
|
||||
@@ -279,21 +254,21 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
{{end}}
|
||||
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=4" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 4</a>
|
||||
<a href="/admin/wizard?step=6" class="btn" style="text-decoration:none">К шагу 6 →</a>
|
||||
<a href="/admin/wizard?step=6" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 6</a>
|
||||
<a href="/admin/wizard?step=8" class="btn" style="text-decoration:none">К шагу 8 →</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 4: НРД ============= */}}
|
||||
{{if eq .Step 6}}
|
||||
{{if eq .Step 8}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.NSD}}ok{{else}}err{{end}}"></span>Шаг 6. Интеграционный шлюз НРД</h2>
|
||||
<h2><span class="dot {{if .Done.NSD}}ok{{else}}err{{end}}"></span>Шаг 8. Интеграционный шлюз НРД</h2>
|
||||
<p>Адрес web-сервиса ONYX и имя ключевого контейнера НРД.</p>
|
||||
|
||||
<div class="help-block">
|
||||
<strong>Что это?</strong> Интеграционный шлюз (ИШ) НРД — это компонент, через который наши M2M-сообщения отправляются в НРД. {{if devOn}}У НРД есть 4 контура: <em>GUEST</em> (для разработки) и <em>TEST3</em> (предпродакшен), каждый в варианте ГОСТ или RSA.{{else}}У НРД есть тестовый контур (<em>TEST3</em>) и боевой контур, каждый в варианте ГОСТ или RSA.{{end}}<br>
|
||||
<strong>Где взять?</strong> Дистрибутив ИШ и инструкции — на сайте НРД <a href="https://www.nsd.ru/workflow/system/programs/" target="_blank">nsd.ru/workflow/system/programs/</a>. Доступ к тестовым контурам выдаётся НРД по заявке (см. <code>DOC/instr_podkl_stend_v3.pdf</code>).
|
||||
<strong>К кому обратиться.</strong> Программу ИШ выпускает НРД (дистрибутив и инструкции — <a href="https://www.nsd.ru/workflow/system/programs/" target="_blank" rel="noopener">nsd.ru/workflow/system/programs</a>). Доступ к контурам НРД (сначала тестовый TEST3, затем боевой) выдаёт НРД по заявке: обратитесь к вашему менеджеру НРД или в клиентскую поддержку по электронному документообороту. НРД сообщит ваш депозитарный код, счёт и раздел — если учёт в DEPO подключён, bj подставит их сам.
|
||||
</div>
|
||||
|
||||
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px">
|
||||
@@ -324,14 +299,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
<div><label>Раздел счёта</label><input type="text" name="section_id" value="{{if .Settings.NSD.SectionID}}{{.Settings.NSD.SectionID}}{{else if .Suggest}}{{.Suggest.SectionID}}{{end}}" placeholder="36MC0000000000F00" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
|
||||
<button type="submit" class="btn" style="justify-self:start">Сохранить</button>
|
||||
</form>
|
||||
{{if .Suggest}}{{if .Suggest.OrgName}}<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px">
|
||||
<p class="muted" style="margin:0">Организация (из DEPO — проверьте): код участника, ИНН, наименование.</p>
|
||||
<input type="hidden" name="return_to" value="/admin/wizard?step=6">
|
||||
<div><label>Код участника</label><input type="text" name="org_code" value="{{if .Settings.NSD.OrgCode}}{{.Settings.NSD.OrgCode}}{{else}}{{.Suggest.OrgCode}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
|
||||
<div><label>ИНН</label><input type="text" name="org_inn" value="{{if .Settings.NSD.OrgINN}}{{.Settings.NSD.OrgINN}}{{else}}{{.Suggest.OrgINN}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
|
||||
<div><label>Наименование</label><input type="text" name="org_name" value="{{if .Settings.NSD.OrgName}}{{.Settings.NSD.OrgName}}{{else}}{{.Suggest.OrgName}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
|
||||
<button type="submit" class="btn" style="justify-self:start">Сохранить организацию</button>
|
||||
</form>{{end}}{{end}}
|
||||
<p class="muted" style="font-size:13px;margin-top:10px">Организация (код участника, ИНН, наименование) — на <a href="/admin/wizard?step=4">шаге 4</a>.</p>
|
||||
<script>
|
||||
// Автозаполнение URL по выбранному профилю
|
||||
document.getElementById('nsd-profile').addEventListener('change', function(e){
|
||||
@@ -348,17 +316,17 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
</script>
|
||||
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=5" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 5</a>
|
||||
{{if .Done.NSD}}<a href="/admin/wizard?step=7" class="btn" style="text-decoration:none">К шагу 7 →</a>{{else}}<a href="/admin/wizard?step=7&skip=nsd" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить ({{if devOn}}mock-режим{{else}}обмен с НРД не настроен{{end}}) →</a>{{end}}
|
||||
<a href="/admin/wizard?step=7" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 7</a>
|
||||
{{if .Done.NSD}}<a href="/admin/wizard?step=9" class="btn" style="text-decoration:none">К шагу 9 →</a>{{else}}<a href="/admin/wizard?step=9&skip=nsd" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить ({{if devOn}}mock-режим{{else}}обмен с НРД не настроен{{end}}) →</a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ============= ШАГ 5: Тест-ран (режим разработки) / проверка подключений ============= */}}
|
||||
{{if eq .Step 7}}
|
||||
{{if eq .Step 9}}
|
||||
<div class="card">
|
||||
{{if devOn}}
|
||||
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 7. Тестовая заявка</h2>
|
||||
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 9. Тестовая заявка</h2>
|
||||
<p>Прогон полного цикла: создание заявки → валидация → подпись → отправка в НРД (или mock) → ожидание Decision → подтверждение.</p>
|
||||
|
||||
<div class="help-block">
|
||||
@@ -379,7 +347,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
</table>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 7. Проверка подключений</h2>
|
||||
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 9. Проверка подключений</h2>
|
||||
<p>Проверьте, что база данных, СКЗИ и шлюз НРД доступны: на странице «Статус» bj проверяет каждое подключение и показывает, что нужно исправить.</p>
|
||||
<a href="/admin/status" class="btn" style="background:var(--ok);font-size:15px;padding:10px 20px;text-decoration:none;display:inline-block;margin-top:12px">Открыть проверку подключений →</a>
|
||||
{{end}}
|
||||
@@ -394,7 +362,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
</table>
|
||||
|
||||
<div style="margin-top:20px;display:flex;justify-content:space-between">
|
||||
<a href="/admin/wizard?step=6" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 6</a>
|
||||
<a href="/admin/wizard?step=8" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 8</a>
|
||||
<a href="/admin/" class="btn" style="text-decoration:none">Перейти к дашборду</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
{{/* Раздел «Обмен Fancy» — один и тот же в «Настройка → Обмен Fancy» и на шаге 3 мастера
|
||||
настройки (данные — SetupData). Правки — только здесь. */}}
|
||||
{{define "fancySection"}}
|
||||
{{template "verline" (.Versions.Get "depo")}}
|
||||
<div class="card" id="sec-fancy-conn">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Подключение к серверу БД клиента</h2>
|
||||
<p class="muted">Сервер учётной базы Fancy DEPO: картотека лиц, счета и договоры, остатки, опции (признак квалифицированного инвестора), трансферные таблицы обмена. Поддерживаются <strong>Microsoft SQL Server</strong> и <strong>PostgreSQL</strong>. База указывается ниже, в карточке «Трансферные таблицы DEPO».</p>
|
||||
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
<label>Тип СУБД
|
||||
<select name="db_type" id="fancy-dbtype" style="width:100%" onchange="var p=document.getElementById('fancy-port');if(!p.value||p.value=='1433'||p.value=='5432'){p.value=this.value=='postgres'?'5432':'1433';}">
|
||||
<option value="mssql" {{if not .Settings.Fancy.IsPostgres}}selected{{end}}>Microsoft SQL Server</option>
|
||||
<option value="postgres" {{if .Settings.Fancy.IsPostgres}}selected{{end}}>PostgreSQL</option>
|
||||
</select></label>
|
||||
<label>Сервер (host или host\instance)
|
||||
<input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="10.10.10.x" style="width:100%" required></label>
|
||||
<label>Порт
|
||||
<input type="number" id="fancy-port" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else}}{{.Settings.Fancy.DefaultPort}}{{end}}" style="width:100%"></label>
|
||||
<label>Пользователь (логин БД)
|
||||
<input type="text" name="user" value="{{.Settings.Fancy.User}}" placeholder="bj_user" style="width:100%" required></label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" placeholder="{{if .Settings.Fancy.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||||
<label>Шифрование канала
|
||||
<select name="encrypt" style="width:100%">
|
||||
<option value="disable" {{if eq .Settings.Fancy.Encrypt "disable"}}selected{{end}}>disable — без TLS (типично для LAN)</option>
|
||||
<option value="true" {{if eq .Settings.Fancy.Encrypt "true"}}selected{{end}}>true — с TLS</option>
|
||||
</select></label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button><button type="submit" formaction="/admin/setup/fancy/test" class="btn">Проверить соединение</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-fancy-depo-tables">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Трансферные таблицы DEPO</h2>
|
||||
<p class="muted">Через эти таблицы bj обменивается с учётной системой DEPO: поручения <code>FTV_M2M</code> и их строки <code>FTV_M2M_LINES</code>, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД <code>FTV_M2M_HANDBOOK</code>. Таблицы создаются в базе DEPO на сервере из подключения выше ({{if .DepoTables.Postgres}}PostgreSQL{{else}}Microsoft SQL Server{{end}}). Создание можно повторять: добавляются только недостающие таблицы, колонки и индексы, данные не меняются.</p>
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<label>База DEPO
|
||||
<input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%"></label>
|
||||
<label>Признак ИИС в названии брокерского договора <span class="muted" style="font-weight:400">(с чего начинается номер договора ИИС в DEPO; несколько — через запятую)</span>
|
||||
<input type="text" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}" placeholder="БРИИС" style="width:100%"></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="auto" {{if eq .Settings.Fancy.DepoTablesMode "auto"}}checked{{end}}> <span>Создаёт bj <span class="muted" style="font-weight:400">— вводите логин с правами на создание таблиц в базе DEPO. Он используется один раз и не сохраняется; bj заодно выдаст права своему логину.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="script" {{if eq .Settings.Fancy.DepoTablesMode "script"}}checked{{end}}> <span>Создаёт администратор СУБД по скрипту <span class="muted" style="font-weight:400">— скачайте скрипт и передайте администратору, после выполнения нажмите «Проверить таблицы».</span></span></label>
|
||||
<div><button type="submit" class="btn">Сохранить</button></div>
|
||||
</form>
|
||||
|
||||
{{if eq .Settings.Fancy.DepoTablesMode "auto"}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:12px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<div><b>Логин с правами на базу {{.DepoTables.DB}}</b><br>
|
||||
<span class="muted" style="font-size:12px">{{if .DepoTables.Postgres}}Владелец базы или суперпользователь.{{else}}Роль db_owner в базе (или db_ddladmin + db_securityadmin).{{end}} Если оставить поля пустыми, bj попробует своим логином {{.DepoTables.User}}.</span></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="{{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="width:100%"></label>
|
||||
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
|
||||
</div>
|
||||
<div><button type="submit" class="btn btn-ok" onclick="return confirm('Создать трансферные таблицы в базе {{.DepoTables.DB}}?')">⚙ Создать таблицы</button></div>
|
||||
</form>
|
||||
{{else if eq .Settings.Fancy.DepoTablesMode "script"}}
|
||||
<div style="margin-top:12px"><b>Скрипт для администратора СУБД</b> — <a href="/admin/setup/fancy/depo-tables/script.sql">скачать {{.DepoTables.ScriptFile}}</a>
|
||||
<pre class="copyable" style="max-height:360px;overflow:auto">{{.DepoTables.Script}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-tables"><button type="submit" class="btn btn-ok">✓ Проверить таблицы</button></form>
|
||||
<span class="muted" style="font-size:12px">логином bj: таблицы на месте, колонки совпадают, есть права на чтение и запись</span>
|
||||
</div>
|
||||
{{if .Settings.Fancy.DepoTablesCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoTablesCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}} <span class="muted">({{.Settings.Fancy.DepoTablesCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-fancy-depo-job">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoJobCheckOK}}ok{{else if .Settings.Fancy.DepoJobMode}}warn{{else}}err{{end}}"></span>Где запускается обработка трансферных таблиц DEPO</h2>
|
||||
<p class="muted">Поручения и события, которые bj пишет в <code>FTV_M2M*</code>, обрабатывает процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO. Выберите, кто её запускает, выполните инструкцию и нажмите «Проверить готовность» — без зелёной отметки обмен с DEPO работать не будет.</p>
|
||||
<form method="post" action="/admin/setup/fancy/depo-job" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-job">
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="bj" {{if eq .Settings.Fancy.DepoJobMode "bj"}}checked{{end}}> <span>Внутри bj <span class="muted" style="font-weight:400">— bj сам вызывает процедуру по расписанию. На сервере клиента нужно одно право на процедуру. Для SQL Server Express (там нет SQL Agent) и PostgreSQL.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-auto" {{if or (eq .Settings.Fancy.DepoJobMode "sql-auto") (not .Settings.Fancy.DepoJobMode)}}checked{{end}}> <span>Джоба SQL Agent, создаёт bj <span class="muted" style="font-weight:400">— рекомендуется: работает независимо от bj. bj создаёт джобу кнопкой — логином администратора SQL (вводится один раз и не сохраняется) или своим логином, если ему выдана роль оператора SQL Agent.</span></span></label>
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-manual" {{if eq .Settings.Fancy.DepoJobMode "sql-manual"}}checked{{end}}> <span>Джоба SQL Agent, настраивает администратор <span class="muted" style="font-weight:400">— передаёте скрипт, bj проверяет, что джоба работает.</span></span></label>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
|
||||
<label>Интервал, секунд <input type="number" name="depo_job_every_sec" min="5" max="3600" value="{{.DepoJob.Every}}" style="width:100%"></label>
|
||||
<label>Процедура <input type="text" name="depo_job_proc" value="{{.Settings.Fancy.DepoJobProc}}" placeholder="dbo.FTV_M2M_JOB" style="width:100%"></label>
|
||||
<label>Работает от имени <input type="text" name="depo_job_run_as" value="{{.Settings.Fancy.DepoJobRunAs}}" placeholder="SYSDBA" style="width:100%"></label>
|
||||
</div>
|
||||
<p class="muted" style="font-size:12px;margin:0">Процедура обработки выполняется от имени пользователя базы DEPO (у разработчика Fancy это <code>SYSDBA</code>). У клиента такого пользователя может не быть или он называется иначе: «Проверить готовность» проверит, что он есть в базе.</p>
|
||||
<div><button type="submit" class="btn">Сохранить способ</button></div>
|
||||
</form>
|
||||
|
||||
{{if eq .Settings.Fancy.DepoJobMode "bj"}}
|
||||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — выполнить один раз:
|
||||
<pre class="copyable">{{.DepoJob.GrantScript}}</pre></div>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "sql-auto"}}
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/create" style="margin-top:12px;display:grid;gap:8px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-job">
|
||||
<div><b>Создать джобу {{.DepoJob.Name}}</b> <span class="muted" style="font-size:12px">— SQL Server Agent должен быть запущен. Логин администратора SQL (sysadmin, например sa) используется один раз и не сохраняется; пусто — bj попробует своим логином.</span></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="sa" style="width:100%"></label>
|
||||
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
|
||||
</div>
|
||||
<div><button type="submit" class="btn btn-ok" onclick="return confirm('Создать джобу SQL Agent {{.DepoJob.Name}} на сервере клиента?')">⚙ Создать джобу и проверить</button></div>
|
||||
</form>
|
||||
<details style="margin-top:8px"><summary class="muted" style="cursor:pointer;font-size:12px">Без логина администратора: выдать логину bj роль оператора SQL Agent (скрипт)</summary>
|
||||
<pre class="copyable">{{.DepoJob.AgentRights}}</pre></details>
|
||||
{{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}}
|
||||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — создать джобу (SQL Agent должен быть запущен, служебная учётная запись Agent должна иметь право выполнять процедуру):
|
||||
<pre class="copyable">{{.DepoJob.AgentScript}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
{{if .Settings.Fancy.DepoJobMode}}
|
||||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/fancy/depo-job/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-job"><button type="submit" class="btn btn-ok">✓ Проверить готовность</button></form>
|
||||
<span class="muted" style="font-size:12px">для вариантов с джобой bj пишет служебное событие BJ_PING и ждёт до 40 с, пока обработчик его отметит</span>
|
||||
</div>
|
||||
{{if .Settings.Fancy.DepoJobCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoJobCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}} <span class="muted">({{.Settings.Fancy.DepoJobCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||||
{{if and (eq .Settings.Fancy.DepoJobMode "bj") .Settings.Fancy.DepoJobLast}}<p class="muted" style="font-size:12px;margin-top:6px">Последний вызов процедуры — {{.Settings.Fancy.DepoJobLast}}</p>{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "verline"}}{{if .Title}}<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">{{.Title}}: {{if .Version}}<strong>{{.Version}}</strong>{{else}}не определена{{end}}{{with .Detail}} · {{.}}{{end}}{{with .Err}} · <span style="color:var(--err)">{{.}}</span>{{end}}</p>{{end}}{{end}}
|
||||
@@ -0,0 +1,67 @@
|
||||
{{/* «Наша организация» — одна и та же в «Настройка → НРД» и в мастере настройки (данные — SetupData). */}}
|
||||
{{define "orgSection"}}
|
||||
<div class="card" id="sec-org">
|
||||
<h2><span class="dot {{if and .OrgINN .OrgCode}}ok{{else}}err{{end}}"></span>Наша организация</h2>
|
||||
<p class="muted">Депозитарий — участник MOEX МОСТ, от имени которого bj отправляет и принимает переводы: ИНН передающего и зачисляющего депозитария в пакетах, код отправителя и код фирмы в QUIK. Выберите себя из справочника участников НРД или введите данные вручную.</p>
|
||||
{{if and .OrgINN .OrgCode}}<p style="margin:8px 0 0"><b>{{if .OrgName}}{{.OrgName}}{{else}}—{{end}}</b> · ИНН <code>{{.OrgINN}}</code> · код <code>{{.OrgCode}}</code>{{if not .Settings.NSD.OrgINN}} <span class="muted">(ИНН взят из справочника по коду)</span>{{end}}</p>
|
||||
{{else}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
|
||||
<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
|
||||
<input type="hidden" name="section" value="sec-org">
|
||||
<label>Найти себя в справочнике участников M2M</label>
|
||||
<div class="hb-wrap"><input id="orgq" placeholder="Выберите из списка или введите ИНН, название, код" autocomplete="off"><button type="button" class="hb-toggle" title="Весь список">▾</button></div>
|
||||
<div id="orgres" class="hb-res"></div>
|
||||
<style>
|
||||
.hb-box{position:relative}
|
||||
.hb-res{margin-top:4px;max-height:320px;overflow:auto;border-radius:8px;border:1px solid var(--border);background:var(--card-2);padding:4px}
|
||||
.hb-res:empty{display:none}
|
||||
.hb-wrap{display:flex;gap:6px}.hb-wrap input{flex:1}
|
||||
.hb-toggle{padding:0 12px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}
|
||||
.hb-hit{display:block;width:100%;text-align:left;padding:7px 10px;margin:2px 0;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;font-size:13px}
|
||||
.hb-hit:hover{border-color:var(--accent)}
|
||||
.hb-m{color:var(--muted);font-size:12px}
|
||||
</style>
|
||||
<script>
|
||||
// hbFind — подбор участника справочника НРД по ИНН, названию или коду (без учёта регистра и кавычек).
|
||||
function hbNorm(s){return String(s||'').toLowerCase().replace(/ё/g,'е').replace(/[«»"'“”.,()]/g,' ').replace(/\s+/g,' ').trim()}
|
||||
function hbFind(list,q){q=hbNorm(q);
|
||||
if(!q)return list.slice().sort((a,b)=>(a.kind===b.kind?0:a.kind==='depo'?-1:1)||hbNorm(a.name).localeCompare(hbNorm(b.name),'ru'));
|
||||
const words=q.split(' ');
|
||||
return list.map(p=>{const hay=hbNorm([p.inn,p.code,p.name,p.full_name,p.display_name,p.eng_name].join(' '));
|
||||
if(!words.every(w=>hay.includes(w)))return null;
|
||||
const score=(String(p.inn).startsWith(q)||String(p.code).toLowerCase().startsWith(q)?0:hbNorm(p.name).startsWith(q)||hbNorm(p.display_name).startsWith(q)?1:2)+(p.kind==='depo'?0:0.5);
|
||||
return {p,score}}).filter(Boolean).sort((a,b)=>a.score-b.score||hbNorm(a.p.name).localeCompare(hbNorm(b.p.name),'ru')).map(x=>x.p)}
|
||||
function hbBind(inp,box,list,onPick){
|
||||
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
||||
let hits=[],picked=inp.dataset.picked==='1';
|
||||
const q=()=>picked?'':inp.value;
|
||||
const draw=()=>{hits=hbFind(list,q());box.innerHTML=(hits.length?'<div class="hb-m" style="padding:2px 4px">'+(q().trim()?'Найдено: ':'Все участники: ')+hits.length+'</div>'+hits.map((p,i)=>'<button type="button" class="hb-hit" data-i="'+i+'" data-inn="'+esc(p.inn)+'" data-code="'+esc(p.code)+'"><b>'+esc(p.name)+'</b> <span class="hb-m">ИНН '+esc(p.inn)+' · '+esc(p.code)+(p.kind==='broker'?' · брокер':'')+'</span></button>').join(''):'<div class="hb-m" style="padding:6px 2px">В справочнике не найдено — введите ИНН и наименование в поля ниже.</div>');box.classList.add('open')};
|
||||
const close=()=>{box.innerHTML='';box.classList.remove('open')};
|
||||
inp.addEventListener('focus',()=>{if(picked)inp.select();draw()});
|
||||
inp.addEventListener('input',()=>{picked=false;draw()});
|
||||
// Enter выбирает только при единственном совпадении: иначе молча ушёл бы первый попавшийся.
|
||||
inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();if(hits.length===1)pick(hits[0]);else if(hits.length>1){const m=box.querySelector('.hb-m');if(m)m.textContent='Найдено: '+hits.length+' — уточните запрос или выберите мышью'}}if(e.key==='Escape')close()});
|
||||
const pick=p=>{onPick(p);picked=true;close()};
|
||||
box.addEventListener('mousedown',e=>e.preventDefault());
|
||||
// Участник — по ИНН и коду с самой кнопки, а не по номеру строки: список мог перерисоваться между показом и нажатием.
|
||||
box.addEventListener('click',e=>{const b=e.target.closest('.hb-hit');if(!b)return;const p=list.find(x=>String(x.inn)===b.dataset.inn&&String(x.code)===b.dataset.code)||hits[+b.dataset.i];if(p)pick(p)});
|
||||
inp.addEventListener('blur',()=>setTimeout(close,150));
|
||||
const tg=inp.parentNode.querySelector('.hb-toggle');
|
||||
if(tg)tg.addEventListener('click',()=>{if(box.classList.contains('open'))close();else{inp.focus();draw()}});
|
||||
}
|
||||
{
|
||||
const inp=document.getElementById('orgq'),f=inp.form;
|
||||
hbBind(inp,document.getElementById('orgres'),{{.Participants}}||[],p=>{f.org_code.value=p.code;f.org_inn.value=p.inn;f.org_name.value=p.full_name||p.name;inp.value=p.name+' · ИНН '+p.inn})
|
||||
}
|
||||
</script>
|
||||
{{if not .Participants}}<span class="muted" style="font-size:12px">Справочник участников ещё не получен — запросите его в «Диагностике» или введите данные вручную.</span>{{end}}
|
||||
<label>Код участника</label>
|
||||
<input type="text" name="org_code" value="{{if .Settings.NSD.OrgCode}}{{.Settings.NSD.OrgCode}}{{else if .Suggest}}{{.Suggest.OrgCode}}{{end}}" placeholder="MC0000000000" maxlength="12">
|
||||
<label>ИНН</label>
|
||||
<input type="text" name="org_inn" value="{{if .Settings.NSD.OrgINN}}{{.Settings.NSD.OrgINN}}{{else if .Suggest}}{{.Suggest.OrgINN}}{{end}}" maxlength="10" inputmode="numeric">
|
||||
<label>Наименование</label>
|
||||
<input type="text" name="org_name" value="{{if .Settings.NSD.OrgName}}{{.Settings.NSD.OrgName}}{{else if .Suggest}}{{.Suggest.OrgName}}{{end}}">
|
||||
{{if .Suggest}}{{if .Suggest.OrgName}}<span class="muted" style="font-size:12px">Из DEPO: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}} — подставлено в пустые поля.</span>{{end}}{{end}}
|
||||
<div><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -0,0 +1,82 @@
|
||||
{{/* «QUIK — модуль неторговых поручений» — один и тот же в настройках и в мастере (данные — SetupData). */}}
|
||||
{{define "quikSection"}}
|
||||
{{template "verline" (.Versions.Get "quik-mnp")}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{else}}err{{end}}"></span>Подключение к БД МНП QUIK (MSSQL)</h2>
|
||||
<p class="muted">При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД <strong>боевая</strong> — проверка только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью <code>InstrEu</code>.<br>
|
||||
<a href="/admin/help/quik"><strong>Инструкция для администратора QUIK</strong></a> — что настроить в QuikAdmin для M2M · <a href="/admin/docs/">документы ARQA</a>.</p>
|
||||
<form method="post" action="/admin/setup/quik" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
<label>Сервер (host или host\instance)
|
||||
<input type="text" name="server" value="{{.Settings.Quik.Server}}" placeholder="адрес SQL Server QUIK" style="width:100%" required></label>
|
||||
<label>Порт
|
||||
<input type="number" name="port" value="{{if .Settings.Quik.Port}}{{.Settings.Quik.Port}}{{else}}1433{{end}}" placeholder="8849" style="width:100%"></label>
|
||||
<label>Имя БД (если требуется, иначе оставьте пустым)
|
||||
<input type="text" name="database" value="{{.Settings.Quik.Database}}" placeholder="(по умолчанию)" style="width:100%"></label>
|
||||
<label>Логин (роль InstrEu)
|
||||
<input type="text" name="user" value="{{.Settings.Quik.User}}" placeholder="логин SQL" style="width:100%" required></label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" placeholder="{{if .Settings.Quik.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||||
<label>Шифрование канала
|
||||
<select name="encrypt" style="width:100%">
|
||||
<option value="true" {{if eq .Settings.Quik.Encrypt "true"}}selected{{end}}>true — с TLS (боевой сервер обычно требует)</option>
|
||||
<option value="disable" {{if eq .Settings.Quik.Encrypt "disable"}}selected{{end}}>disable — без TLS</option>
|
||||
</select></label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400">
|
||||
<input type="checkbox" name="trust" value="1" {{if .Settings.Quik.Trust}}checked{{end}}>
|
||||
Доверять сертификату сервера (TrustServerCertificate — для самоподписанного)</label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button type="submit" class="btn btn-ok">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
|
||||
<form method="post" action="/admin/setup/quik/test" style="margin:0"><button type="submit" class="btn">Проверить подключение (чтение)</button></form>
|
||||
</div>
|
||||
<p class="muted" style="margin-top:12px">Статус: {{if .Settings.Quik.Verified}}<span style="color:var(--ok)">✓ подключение проверено</span>{{else}}<span class="muted">не проверено</span>{{end}}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="sec-quik-cycle">
|
||||
<h2><span class="dot {{if .Settings.Quik.Emulate}}warn{{else if not .Settings.Quik.WritesEnabled}}warn{{else if or .Settings.Quik.AutoOut .Settings.Quik.AutoIn}}ok{{else}}warn{{end}}"></span>Автоматический цикл M2M в МНП{{if .Settings.Quik.Emulate}} <span class="hero-status warn">{{if devOn}}имитация{{else}}остановлен{{end}}</span>{{end}}</h2>
|
||||
<p class="muted">Что делает bj в Модуле неторговых поручений по ходу перевода: <strong>исходящий</strong> — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; <strong>входящий</strong> — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Каждый шаг виден в карточке перевода и на странице <a href="/admin/quik">QUIK МНП</a>.</p>
|
||||
<form method="post" action="/admin/setup/quik/cycle" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||||
<input type="hidden" name="section" value="sec-quik-cycle">
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||||
<input type="checkbox" name="writes_enabled" value="1" {{if .Settings.Quik.WritesEnabled}}checked{{end}}>
|
||||
Разрешить записи в МНП (главный предохранитель — база боевая)</label>
|
||||
{{if devOn}} <label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||||
<input type="checkbox" name="emulate" value="1" {{if .Settings.Quik.Emulate}}checked{{end}}>
|
||||
Имитация: шаги цикла (блокировка, подпись, снятие, зачисление) проходят и отмечаются «имитация», в МНП ничего не пишется — пока нет пользователя QUIK для блокировок и лимитов. Имеет приоритет над записью.</label>{{end}}
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="auto_out" value="1" {{if .Settings.Quik.AutoOut}}checked{{end}}>
|
||||
Исходящие переводы: регистрировать при отправке, подписывать / снимать по ответу автоматически</label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="auto_in" value="1" {{if .Settings.Quik.AutoIn}}checked{{end}}>
|
||||
Входящие переводы: зачислять в QUIK после приёма поручения НРД автоматически</label>
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||||
<input type="checkbox" name="poll_terminal" value="1" {{if .Settings.Quik.PollTerminal}}checked{{end}}>
|
||||
Принимаем поручения M2M из QUIK: клиентам разрешена подача «Вывод ЦБ (M2M)» из терминала / мобильного приложения — bj опрашивает их и создаёт заявку оператору. Если фирма такую подачу не разрешает, оставьте выключенным: раздел «Поручения QUIK» их показывать не будет.</label>
|
||||
<label>Торговый счёт для регистрации поручений
|
||||
<input type="text" name="account" value="{{.Settings.Quik.Account}}" placeholder="L01+00000F00" style="width:100%"></label>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label>МНП-инструмент вывода <span class="muted">(пусто = взять из класса INSTROUTM2M)</span>
|
||||
<input type="text" name="sec_out" value="{{.Settings.Quik.SecOut}}" placeholder="RUROUTM2M" style="width:100%"></label>
|
||||
<label>МНП-инструмент ввода <span class="muted">(пусто = из класса INSTRINM2M)</span>
|
||||
<input type="text" name="sec_in" value="{{.Settings.Quik.SecIn}}" placeholder="RURINM2M" style="width:100%"></label>
|
||||
</div>
|
||||
<label>Исключения кодов клиентов <span class="muted">(по умолчанию «БР-001» → «BR001»; строка «номер портфеля=код QUIK»)</span>
|
||||
<textarea name="client_code_map" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:12px" placeholder="БР-4-4=BR44">{{quikCodeMap .Settings.Quik.ClientCodeMap}}</textarea></label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{if .Settings.Quik.LastReport}}
|
||||
<div class="card">
|
||||
<h2>Полный ответ сервера (последняя проверка)</h2>
|
||||
<p class="muted">Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK.
|
||||
{{if not .Settings.Quik.LastAt.IsZero}}Проверка от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC.{{end}}</p>
|
||||
<div style="display:flex;gap:8px;margin:8px 0">
|
||||
<button type="button" class="btn" onclick="var t=document.getElementById('quik-report').textContent;navigator.clipboard&&navigator.clipboard.writeText(t);this.textContent='Скопировано ✓';setTimeout(()=>this.textContent='Скопировать отчёт',1500)">Скопировать отчёт</button>
|
||||
</div>
|
||||
<pre id="quik-report" style="white-space:pre-wrap;word-break:break-word;background:var(--surface-2,#0d1117);color:var(--code-fg,#c9d1d9);padding:14px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;max-height:420px;user-select:all">{{.Settings.Quik.LastReport}}</pre>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user