Мастер, шаг 3 DEPO — по экранам: описание → подключение (проверка сразу) → таблицы (bj или скрипт) → обработка (job) → готово; справочник участников M2M встроен снимком для чистой установки

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-24 23:24:03 +03:00
parent d7b53f1c16
commit 29e9dbcfbc
11 changed files with 302 additions and 19 deletions
+2 -2
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"html/template"
"net/http"
"os"
"path"
"strings"
"time"
@@ -53,6 +52,7 @@ var templateFuncs = template.FuncMap{
"devOn": authTemplateFuncs["devOn"],
"devLicensed": authTemplateFuncs["devLicensed"],
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
"depoSubs": func() any { return depoWizardSubs }, // экраны шага 3 мастера
"authOpen": authOpen,
"internalDocs": func() bool { return internalDocsBuilt },
"roleTitle": roleTitle,
@@ -683,7 +683,7 @@ type handbookData struct {
func (a *admin) renderHandbook(w http.ResponseWriter, _ *http.Request) {
data := handbookData{page: nowPage("Справочник участников", "handbook")}
data.CodesHTML = template.HTML(m2mSpravkaHTML(false)) // справка по кодам ошибок
if raw, err := os.ReadFile(handbookFile); err == nil {
if raw, err := readHandbook(); err == nil {
var hb struct {
At string `json:"at"`
Participants []HandbookEntry `json:"participants"`
+1 -1
View File
@@ -889,7 +889,7 @@ func devOnlyPath(p string) bool {
func moduleForPath(p string) string {
has := func(pre string) bool { return p == pre || strings.HasPrefix(p, pre+"/") }
switch {
case has("/admin/quik"), has("/admin/setup/quik"), has("/admin/fancy-queue"), has("/admin/setup/fancy"), has("/admin/manual/depo"):
case has("/admin/quik"), has("/admin/setup/quik"), has("/admin/fancy-queue"), has("/admin/setup/fancy"), has("/admin/setup/wizard-depo"), has("/admin/manual/depo"):
return "quik-mnp"
case has("/admin/setup/quikexport"):
return "quik-export"
+2 -2
View File
@@ -273,7 +273,7 @@ func depoUpsertOrder(rc *RuntimeConfig, guid, direction, cpCode, deponentName, d
// handbookCodeByINN — MC-код депозитария по ИНН из сохранённого справочника.
func handbookCodeByINN(inn string) string {
raw, err := os.ReadFile(handbookFile)
raw, err := readHandbook()
if err != nil {
return ""
}
@@ -542,7 +542,7 @@ func depoPollOrders(ctx context.Context, db *sql.DB, rc *RuntimeConfig) {
var depoHandbookWritten time.Time
func depoWriteHandbook(rc *RuntimeConfig) error {
raw, err := os.ReadFile(handbookFile)
raw, err := readHandbook()
if err != nil {
return err
}
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
package lkgateway
import (
_ "embed"
"os"
)
// Снимок справочника участников M2M (получен от НРД, открытые данные): на чистой установке
// шлюза НРД ещё нет, и справочник не получить — тогда поиск организации и остальные места
// работают по снимку, поставляемому с bj. Как только bj получит справочник от НРД (M2MTH),
// используется он. Снимок обновляется с выпусками bj.
//
//go:embed handbook/m2m-handbook.json
var handbookSnapshot []byte
// readHandbook — справочник от НРД, если он уже получен, иначе встроенный снимок.
func readHandbook() ([]byte, error) {
if b, err := os.ReadFile(handbookFile); err == nil && len(b) > 0 {
return b, nil
}
return handbookSnapshot, nil
}
+1 -1
View File
@@ -179,7 +179,7 @@ func handbookNameByCode(code string) string {
if code == "" {
return ""
}
raw, err := os.ReadFile(handbookFile)
raw, err := readHandbook()
if err != nil {
return ""
}
+1 -1
View File
@@ -744,7 +744,7 @@ func handbookLoad() {
}
byCode, byINN, codeINN := map[string]string{}, map[string]string{}, map[string]string{}
var entries []HandbookEntry
if b, err := os.ReadFile(handbookFile); err == nil {
if b, err := readHandbook(); err == nil {
var doc struct {
Participants []HandbookEntry `json:"participants"`
Places []HandbookEntry `json:"settlement_places"`
+1 -2
View File
@@ -7,7 +7,6 @@ package lkgateway
import (
"encoding/json"
"net/http"
"os"
"regexp"
"strings"
)
@@ -16,7 +15,7 @@ var regexpMCCode = regexp.MustCompile(`^[A-Z]{2}[0-9A-Z]{10}$`)
// handbookParticipants — участники M2M из справочника НРД (без мест расчётов).
func handbookParticipants() []HandbookEntry {
raw, err := os.ReadFile(handbookFile)
raw, err := readHandbook()
if err != nil {
return nil
}
+11
View File
@@ -80,6 +80,10 @@ func registerSetup(mux *http.ServeMux, a *admin, rc *RuntimeConfig, svc *Service
mux.HandleFunc("/admin/setup/update/apply", h.applyUpdate)
mux.HandleFunc("/admin/setup/update/upload", h.uploadUpdate)
mux.HandleFunc("/admin/setup/update/progress", h.updateProgress)
mux.HandleFunc("/admin/setup/wizard-depo/conn", h.wizardDepoConn)
mux.HandleFunc("/admin/setup/wizard-depo/tables", h.wizardDepoTables)
mux.HandleFunc("/admin/setup/wizard-depo/job", h.wizardDepoJob)
mux.HandleFunc("/admin/setup/wizard-depo/recheck", h.wizardDepoRecheck)
mux.HandleFunc("/admin/setup/license", h.saveLicense)
mux.HandleFunc("/admin/setup/license/activate", h.licenseActivate)
mux.HandleFunc("/admin/setup/license/renew", h.licenseRenewNow)
@@ -176,6 +180,7 @@ type WizardData struct {
DepoAgent bool // у SQL Server есть SQL Agent (не Express, не PostgreSQL)
DepoJobDef string // способ обработки по умолчанию для мастера
Fancy *SetupData // шаг 3: общий блок «Обмен Fancy» (тот же, что в настройках)
Sub string // экран шага 3 (учёт DEPO): intro|conn|tables|job|done
ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена)
CryptoSvc string // состояние службы bj-crypto (systemctl is-active)
PGSummary string // куда подключена база bj, без пароля
@@ -285,6 +290,12 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
fd := h.setupData(w, r, "")
d.Fancy = &fd
}
if step == 3 {
d.Sub = depoWizardSub(r.URL.Query().Get("sub"))
if r.URL.Query().Get("sub") == "" && d.Done.Depo {
d.Sub = "done"
}
}
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) != "" {
@@ -84,24 +84,89 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
</div>
{{end}}
{{/* ============= ШАГ 3: Учёт DEPO (Fancy) ============= */}}
{{/* ============= ШАГ 3: Учёт DEPO (Fancy) — по экранам ============= */}}
{{if eq .Step 3}}
<div class="card">
<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, логин и пароль даёт ваш администратор баз данных (тот, кто обслуживает сервер учёта Fancy). Процедуру обработки обмена в базе DEPO ставит разработчик вашей системы учёта Fancy — если её ещё нет, bj подскажет на проверке.<br>
<strong>Что ввести здесь.</strong> Адрес сервера, логин и пароль. Всё остальное — таблицы обмена, джобу обработки, реквизиты вашей организации — bj подготовит сам; для таблиц и джобы понадобится разово логин администратора SQL, он не сохраняется.</div>
{{if not .DepoLicensed}}
<p class="muted">Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается. После добавления модуля в лицензию он станет доступен здесь и в «Настройка → Обмен Fancy».</p>
<p class="muted">Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается.</p>
{{else}}
<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}}
{{$sub := .Sub}}
<div style="display:flex;gap:6px;flex-wrap:wrap;margin:6px 0 14px">{{range $i, $x := depoSubs}}<span style="font-size:12px;padding:4px 10px;border-radius:12px;{{if eq $x.Key $sub}}background:var(--accent);color:#fff{{else}}background:var(--card-2);color:var(--muted){{end}}">{{add $i 1}}. {{$x.Title}}</span>{{end}}</div>
{{if eq $sub "intro"}}
<div class="help-block"><strong>Что это.</strong> Подключение bj к базе депозитарного учёта DEPO (Fancy).<br>
<strong>Зачем.</strong> bj сверяет с учётом каждый перевод (клиент, счёт, остаток, квалификация) и сам передаёт в DEPO поручения и их исполнение — документы в учёте создаются автоматически. Отсюда же bj возьмёт реквизиты вашей организации (шаг 4).<br>
<strong>Что понадобится.</strong> Адрес сервера базы DEPO, логин и пароль — их даёт ваш администратор баз данных. Для создания таблиц обмена и джобы обработки может понадобиться разово логин администратора SQL — он не сохраняется. Процедуру обработки обмена в базе DEPO ставит разработчик системы учёта Fancy; если её ещё нет, bj скажет об этом на проверке.<br>
<strong>Как пойдём.</strong> Подключение → таблицы обмена → обработка → проверка. Каждый шаг проверяется сразу.</div>
<div style="margin-top:14px"><a href="/admin/wizard?step=3&sub=conn" class="btn" style="text-decoration:none">Начать →</a></div>
{{else if eq $sub "conn"}}
<p>Адрес сервера учётной базы DEPO и логин, под которым bj будет с ней работать. По «Далее» bj сохранит и сразу проверит подключение.</p>
<form method="post" action="/admin/setup/wizard-depo/conn" style="display:grid;gap:10px;max-width:560px">
<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}}>Microsoft 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" required 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>База DEPO</label><input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.Settings.Fancy.DepoDB}}" pattern="[A-Za-z][A-Za-z0-9_]*" 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" required 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;justify-content:space-between;margin-top:6px"><a href="/admin/wizard?step=3&sub=intro" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← Назад</a><button type="submit" class="btn">Проверить и далее →</button></div>
</form>
{{else if eq $sub "tables"}}
<p>Четыре таблицы обмена <code>FTV_M2M*</code> в базе {{.DepoTables.DB}}: через них bj передаёт поручения и события в DEPO. Данные учёта они не трогают. Кто их создаёт?</p>
<form method="post" action="/admin/setup/wizard-depo/tables" style="display:grid;gap:10px;max-width:680px" id="wiz-tables">
<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_tables_mode" value="auto" {{if ne .Settings.Fancy.DepoTablesMode "script"}}checked{{end}} onchange="wizTbl()"> <span><b>bj создаёт сам</b> <span class="muted" style="font-size:13px">— по «Далее». Если у логина {{.DepoTables.User}} нет прав на создание таблиц, введите разово логин администратора SQL (не сохраняется).</span></span></label>
<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_tables_mode" value="script" {{if eq .Settings.Fancy.DepoTablesMode "script"}}checked{{end}} onchange="wizTbl()"> <span><b>Создаёт администратор по скрипту</b> <span class="muted" style="font-size:13px">— передайте ему скрипт, после выполнения нажмите «Проверить и далее».</span></span></label>
<div id="wiz-tbl-auto" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин администратора (необязательно)" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
</div>
<div id="wiz-tbl-script" style="display:none"><a href="/admin/setup/fancy/depo-tables/script.sql">Скачать скрипт {{.DepoTables.ScriptFile}}</a>
<details style="margin-top:6px"><summary style="cursor:pointer;font-size:13px">Посмотреть скрипт</summary><pre class="copyable" style="max-height:300px;overflow:auto;font-size:12px">{{.DepoTables.Script}}</pre></details>
<input type="hidden" name="action" value="" id="wiz-tbl-action"></div>
<div style="display:flex;justify-content:space-between;margin-top:6px"><a href="/admin/wizard?step=3&sub=conn" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← Назад</a><button type="submit" class="btn" id="wiz-tbl-btn">Создать и далее →</button></div>
</form>
<script>function wizTbl(){var f=document.getElementById('wiz-tables'),sc=f.depo_tables_mode.value==='script';
document.getElementById('wiz-tbl-auto').style.display=sc?'none':'grid';document.getElementById('wiz-tbl-script').style.display=sc?'block':'none';
document.getElementById('wiz-tbl-action').value=sc?'check':'';document.getElementById('wiz-tbl-btn').textContent=sc?'Проверить и далее →':'Создать и далее →';}wizTbl();</script>
{{else if eq $sub "job"}}
<p>Процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO каждые {{.DepoJob.Every}} с превращает записи bj в документы и операции учёта. Кто её запускает?</p>
{{if .DepoEdition}}<p class="muted" style="font-size:13px">Ваш сервер: {{.DepoEdition}} — {{if .DepoAgent}}есть SQL Server Agent, рекомендуем первый вариант.{{else}}без SQL Server Agent, подходит вариант «запускает bj».{{end}}</p>{{end}}
{{$mode := .Settings.Fancy.DepoJobMode}}{{if not $mode}}{{$mode = .DepoJobDef}}{{end}}{{if not $mode}}{{$mode = "sql-auto"}}{{end}}
<form method="post" action="/admin/setup/wizard-depo/job" style="display:grid;gap:10px;max-width:680px" id="wiz-job">
{{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}} onchange="wizJob()"> <span><b>Джоба SQL Server Agent, создаёт bj</b> <span class="muted" style="font-size:13px">— рекомендуется: работает независимо от bj. По «Далее» bj создаст джобу; нужен разово логин администратора SQL (sa) — не сохраняется.</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}} onchange="wizJob()"> <span><b>Запускает сам bj</b> <span class="muted" style="font-size:13px">— для SQL Server Express и PostgreSQL. Администратору SQL нужно выдать одно право (скрипт ниже).</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}} onchange="wizJob()"> <span><b>Джоба SQL Server Agent, создаёт администратор</b> <span class="muted" style="font-size:13px">— передайте ему скрипт.</span></span></label>{{end}}
<div id="wiz-job-auto" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин администратора SQL, например sa" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
</div>
<div id="wiz-job-bj" style="display:none"><p style="margin:0 0 4px;font-size:13px"><b>Администратору SQL — выполнить один раз:</b></p><pre class="copyable" style="font-size:12px">{{.DepoJob.GrantScript}}</pre></div>
<div id="wiz-job-manual" style="display:none"><p style="margin:0 0 4px;font-size:13px"><b>Скрипт для администратора SQL:</b></p><pre class="copyable" style="font-size:12px;max-height:260px;overflow:auto">{{.DepoJob.AgentScript}}</pre></div>
<input type="hidden" name="action" value="" id="wiz-job-action">
<div style="display:flex;justify-content:space-between;margin-top:6px"><a href="/admin/wizard?step=3&sub=tables" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← Назад</a><button type="submit" class="btn" id="wiz-job-btn">Создать, проверить и далее →</button></div>
<p class="muted" style="font-size:12px;margin:0">Проверка: bj кладёт служебное событие и ждёт до 40 секунд, пока обработка его отметит.</p>
</form>
<script>function wizJob(){var f=document.getElementById('wiz-job'),m=f.depo_job_mode.value;
document.getElementById('wiz-job-auto').style.display=m==='sql-auto'?'grid':'none';document.getElementById('wiz-job-bj').style.display=m==='bj'?'block':'none';document.getElementById('wiz-job-manual').style.display=m==='sql-manual'?'block':'none';
document.getElementById('wiz-job-action').value=m==='sql-auto'?'':'check';document.getElementById('wiz-job-btn').textContent=m==='sql-auto'?'Создать, проверить и далее →':'Проверить и далее →';}wizJob();</script>
{{else if eq $sub "done"}}
<table style="margin:6px 0 10px">
<tr><td>Подключение к базе DEPO</td><td>{{if .Settings.Fancy.Server}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.Server}}, база {{.DepoTables.DB}}</span>{{else}}<span style="color:var(--err)">не настроено</span>{{end}}</td></tr>
<tr><td>Таблицы обмена FTV_M2M</td><td>{{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok)">✓ на месте</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoTablesCheck}}</span>{{end}}</td></tr>
<tr><td>Обработка (job)</td><td>{{if .Settings.Fancy.DepoJobCheckOK}}<span style="color:var(--ok)">✓ работает</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoJobCheck}}</span>{{end}}</td></tr>
</table>
{{if .Done.Depo}}<p>Учёт DEPO подключён и проверен. Можно переходить к шагу 4 — там bj предложит реквизиты вашей организации из DEPO.</p>{{end}}
<form method="post" action="/admin/setup/wizard-depo/recheck" style="margin:0"><button type="submit" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Перепроверить всё</button></form>
{{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>
<a href="/admin/wizard?step=4" class="btn" style="{{if not .Done.Depo}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Depo}}К шагу 4 →{{else}}Пропустить →{{end}}</a></div>
<a href="/admin/wizard?step=4" class="btn" style="{{if not .Done.Depo}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Depo}}К шагу 4 →{{else}}Пропустить шаг →{{end}}</a></div>
</div>
{{end}}
{{/* ============= ШАГ 1: PostgreSQL ============= */}}
+185
View File
@@ -0,0 +1,185 @@
package lkgateway
// Шаг 3 мастера настройки (учёт DEPO) — маленький мастер по экранам вместо простыни настроек:
// описание → подключение → трансферные таблицы → обработка (job) → готово. Каждый экран делает
// своё действие по «Далее» и сразу проверяет результат; при ошибке экран не меняется, ошибка
// показывается понятным текстом. Полный раздел «Обмен Fancy» остаётся в «Настройке».
import (
"context"
"net/http"
"net/url"
"strings"
"time"
)
// depoWizardSubs — экраны шага 3 по порядку.
var depoWizardSubs = []struct{ Key, Title string }{
{"intro", "Что это"},
{"conn", "Подключение"},
{"tables", "Таблицы обмена"},
{"job", "Обработка"},
{"done", "Готово"},
}
func depoWizardSub(v string) string {
for _, s := range depoWizardSubs {
if s.Key == v {
return v
}
}
return "intro"
}
// wizDepoGo — перейти на экран шага 3 с сообщением (ok=false — сообщение об ошибке).
func wizDepoGo(w http.ResponseWriter, r *http.Request, sub, msg string, ok bool) {
u := "/admin/wizard?step=3&sub=" + sub
if msg != "" {
if ok {
msg = "✓ " + msg
} else {
msg = "✗ " + msg
}
u += "&flash=" + url.QueryEscape(msg)
}
http.Redirect(w, r, u, http.StatusSeeOther)
}
// wizardDepoConn — POST /admin/setup/wizard-depo/conn: сохранить подключение и сразу проверить его.
func (h *setupHandlers) wizardDepoConn(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || !requireDepo(w, r) {
return
}
s := h.fancyFromForm(r)
if db := strings.TrimSpace(r.FormValue("depo_database")); db != "" {
if !validDBName(db) {
wizDepoGo(w, r, "conn", "имя базы DEPO — только латинские буквы, цифры и _, начинается с буквы", false)
return
}
if db != s.DepoDatabase {
s.DepoTablesCheck, s.DepoTablesCheckOK = "", false
}
s.DepoDatabase = db
}
if s.Server == "" || s.User == "" {
wizDepoGo(w, r, "conn", "укажите адрес сервера и логин", false)
return
}
if err := h.rc.UpdateFancy(s); err != nil {
wizDepoGo(w, r, "conn", "не сохранилось: "+err.Error(), false)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
if _, err := fancyPing(ctx, s, "master"); err != nil {
wizDepoGo(w, r, "conn", "к серверу подключиться не удалось — "+err.Error()+". Проверьте адрес, порт, логин и пароль.", false)
return
}
if _, err := fancyPing(ctx, s, s.DepoDB()); err != nil {
wizDepoGo(w, r, "conn", "сервер отвечает, но база "+s.DepoDB()+" недоступна этим логином — "+err.Error(), false)
return
}
wizDepoGo(w, r, "tables", "подключение к "+s.Server+", база "+s.DepoDB()+" — работает", true)
}
// wizardDepoTables — POST /admin/setup/wizard-depo/tables: создать таблицы (bj) или проверить созданные
// администратором по скрипту.
func (h *setupHandlers) wizardDepoTables(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || !requireDepo(w, r) {
return
}
s := h.rc.Snapshot().Fancy
mode := r.FormValue("depo_tables_mode")
if mode != "script" {
mode = "auto"
}
if s.DepoTablesMode != mode {
s.DepoTablesMode = mode
_ = h.rc.UpdateFancy(s)
}
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
defer cancel()
if mode == "auto" && r.FormValue("action") != "check" {
if _, err := depoTablesCreate(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil {
wizDepoGo(w, r, "tables", "таблицы не созданы — "+err.Error()+". Если у логина bj нет прав, введите разово логин администратора SQL.", false)
return
}
}
msg, ok := depoTablesCheck(ctx, s)
h.saveDepoTablesCheck(msg, ok)
if !ok {
wizDepoGo(w, r, "tables", msg, false)
return
}
if !s.IsPostgres() {
_ = depoWriteHandbook(h.rc)
}
wizDepoGo(w, r, "job", "таблицы обмена на месте: "+msg, true)
}
// wizardDepoJob — POST /admin/setup/wizard-depo/job: выбрать, кто запускает обработку, сделать нужное
// (для «джобу создаёт bj» — создать) и проверить служебным событием.
func (h *setupHandlers) wizardDepoJob(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || !requireDepo(w, r) {
return
}
s := h.rc.Snapshot().Fancy
mode := r.FormValue("depo_job_mode")
if mode != "bj" && mode != "sql-auto" && mode != "sql-manual" {
mode = "sql-auto"
}
if s.DepoJobMode != mode {
s.DepoJobCheck, s.DepoJobCheckOK = "", false
}
s.DepoJobMode, s.DepoJobRun = mode, mode == "bj"
if err := h.rc.UpdateFancy(s); err != nil {
wizDepoGo(w, r, "job", "не сохранилось: "+err.Error(), false)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 90*time.Second)
defer cancel()
if mode == "sql-auto" && r.FormValue("action") != "check" {
if err := depoJobCreateAgent(ctx, h.rc, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil && !strings.Contains(err.Error(), "уже существует") {
wizDepoGo(w, r, "job", "джоба не создана — "+err.Error(), false)
return
}
}
msg, ok := depoJobCheck(ctx, h.rc)
s = h.rc.Snapshot().Fancy
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = msg, ok, time.Now()
_ = h.rc.UpdateFancy(s)
if !ok {
wizDepoGo(w, r, "job", "обработка пока не работает — "+msg, false)
return
}
wizDepoGo(w, r, "done", "обработка работает: "+msg, true)
}
// wizardDepoRecheck — POST /admin/setup/wizard-depo/recheck: перепроверить всё на экране «Готово».
func (h *setupHandlers) wizardDepoRecheck(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || !requireDepo(w, r) {
return
}
s := h.rc.Snapshot().Fancy
ctx, cancel := context.WithTimeout(r.Context(), 90*time.Second)
defer cancel()
if _, err := fancyPing(ctx, s, s.DepoDB()); err != nil {
wizDepoGo(w, r, "conn", "подключение не работает — "+err.Error(), false)
return
}
msg, ok := depoTablesCheck(ctx, s)
h.saveDepoTablesCheck(msg, ok)
if !ok {
wizDepoGo(w, r, "tables", msg, false)
return
}
jmsg, jok := depoJobCheck(ctx, h.rc)
s = h.rc.Snapshot().Fancy
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = jmsg, jok, time.Now()
_ = h.rc.UpdateFancy(s)
if !jok {
wizDepoGo(w, r, "job", jmsg, false)
return
}
wizDepoGo(w, r, "done", "всё перепроверено", true)
}