From e043e7c37120e3000bd966cd5ba4da78c3a2dfa3 Mon Sep 17 00:00:00 2001 From: zuevav Date: Thu, 24 Sep 2026 22:11:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B0=D1=81=D1=82=D0=B5=D1=80,=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B3=203=20DEPO:=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=E2=86=92=20=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=81=D1=84=D0=B5=D1=80=D0=BD=D1=8B=D0=B5=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=E2=86=92=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20(job);=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E=20=D0=B4?= =?UTF-8?q?=D0=B6=D0=BE=D0=B1=D1=83=20SQL=20Agent=20=D1=81=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D1=91=D1=82=20bj=20=D1=80=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=B8=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - редакция SQL Server определяется (Express/PostgreSQL — обработку запускает bj); - создание джобы и таблиц — логином администратора, который не сохраняется; - настройки обмена с DEPO открыты без пароля перспективных модулей, если QUIK МНП в лицензии. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- internal/lkgateway/advanced_gate.go | 9 +++ internal/lkgateway/depoevents.go | 10 ++- internal/lkgateway/fancydb_handlers.go | 14 ++--- internal/lkgateway/setup.go | 38 ++++++++++- .../lkgateway/web/templates/admin_wizard.html | 63 ++++++++++++++++++- 5 files changed, 123 insertions(+), 11 deletions(-) diff --git a/internal/lkgateway/advanced_gate.go b/internal/lkgateway/advanced_gate.go index 21c36c3..b64bf46 100644 --- a/internal/lkgateway/advanced_gate.go +++ b/internal/lkgateway/advanced_gate.go @@ -50,6 +50,15 @@ func advancedUnlocked(r *http.Request) bool { // requireAdvanced — защита ручек сохранения перспективных модулей: если доступ // не открыт, отвечает 403. true — можно продолжать. +// requireDepo — настройки обмена с DEPO: открыты, если модуль «QUIK: неторговые поручения» +// входит в лицензию (мастер настройки, шаг 3), иначе — как перспективный модуль, по паролю. +func requireDepo(w http.ResponseWriter, r *http.Request) bool { + if globalRC != nil && moduleAllowed(globalRC, "quik-mnp") { + return true + } + return requireAdvanced(w, r) +} + func requireAdvanced(w http.ResponseWriter, r *http.Request) bool { if advancedUnlocked(r) { return true diff --git a/internal/lkgateway/depoevents.go b/internal/lkgateway/depoevents.go index edb6b60..adde0ff 100644 --- a/internal/lkgateway/depoevents.go +++ b/internal/lkgateway/depoevents.go @@ -822,9 +822,15 @@ func depoJobCheck(ctx context.Context, rc *RuntimeConfig) (string, bool) { } // depoJobCreateAgent — создать джобу SQL Agent от имени логина bj (нужна роль SQLAgentOperatorRole). -func depoJobCreateAgent(ctx context.Context, rc *RuntimeConfig) error { +// depoJobCreateAgent — создать джобу SQL Agent. adminUser/adminPass — логин администратора SQL +// (sysadmin или SQLAgentOperatorRole в msdb): используется один раз и не сохраняется; пусто — логином bj. +func depoJobCreateAgent(ctx context.Context, rc *RuntimeConfig, adminUser, adminPass string) error { f := rc.Snapshot().Fancy - mdb, err := fancyOpen(f, "msdb") + conn := f + if strings.TrimSpace(adminUser) != "" { + conn.User, conn.Password = strings.TrimSpace(adminUser), adminPass + } + mdb, err := fancyOpen(conn, "msdb") if err != nil { return fmt.Errorf("подключение к msdb: %w", err) } diff --git a/internal/lkgateway/fancydb_handlers.go b/internal/lkgateway/fancydb_handlers.go index 5e6c67a..f63086c 100644 --- a/internal/lkgateway/fancydb_handlers.go +++ b/internal/lkgateway/fancydb_handlers.go @@ -65,7 +65,7 @@ func (h *setupHandlers) fancyFromForm(r *http.Request) FancyMSSQLSettings { // saveFancyDepoTables — POST /admin/setup/fancy/depo-tables: база DEPO и способ создания таблиц. func (h *setupHandlers) saveFancyDepoTables(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { @@ -112,7 +112,7 @@ func (h *setupHandlers) saveDepoTablesCheck(msg string, ok bool) { // createFancyDepoTables — POST /admin/setup/fancy/depo-tables/create: bj создаёт таблицы сам. // Логин с правами берётся из формы и нигде не сохраняется. func (h *setupHandlers) createFancyDepoTables(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { @@ -181,7 +181,7 @@ func (h *setupHandlers) fancyDepoTablesScript(w http.ResponseWriter, r *http.Req // saveFancyDepoJob — POST /admin/setup/fancy/depo-job: обработка трансферных таблиц силами bj. func (h *setupHandlers) saveFancyDepoJob(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { @@ -226,7 +226,7 @@ func (h *setupHandlers) saveFancyDepoJob(w http.ResponseWriter, r *http.Request) // checkFancyDepoJob — POST /admin/setup/fancy/depo-job/check: проверка готовности выбранного способа. func (h *setupHandlers) checkFancyDepoJob(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { @@ -248,7 +248,7 @@ func (h *setupHandlers) checkFancyDepoJob(w http.ResponseWriter, r *http.Request // createFancyDepoJob — POST /admin/setup/fancy/depo-job/create: создать джобу SQL Agent от имени bj. func (h *setupHandlers) createFancyDepoJob(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { @@ -257,7 +257,7 @@ func (h *setupHandlers) createFancyDepoJob(w http.ResponseWriter, r *http.Reques } ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) defer cancel() - if err := depoJobCreateAgent(ctx, h.rc); err != nil { + if err := depoJobCreateAgent(ctx, h.rc, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil { setupFlash(w, r, "Создание джобы: "+err.Error()) return } @@ -270,7 +270,7 @@ func (h *setupHandlers) createFancyDepoJob(w http.ResponseWriter, r *http.Reques // saveFancy — POST /admin/setup/fancy. Сохраняет параметры подключения. func (h *setupHandlers) saveFancy(w http.ResponseWriter, r *http.Request) { - if !requireAdvanced(w, r) { + if !requireDepo(w, r) { return } if r.Method != http.MethodPost { diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 29834dc..b33e5f8 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -170,6 +170,11 @@ type WizardData struct { Lic LicenseStatus DepoLicensed bool DepoDB string + DepoTables DepoTablesView + DepoJob DepoJobView + DepoEdition string // редакция SQL Server клиента (пусто — не определена) + DepoAgent bool // у SQL Server есть SQL Agent (не Express, не PostgreSQL) + DepoJobDef string // способ обработки по умолчанию для мастера ValidataVer string // версия установленной «Валидаты Клиент L» (пусто — не установлена) CryptoSvc string // состояние службы bj-crypto (systemctl is-active) PGSummary string // куда подключена база bj, без пароля @@ -208,7 +213,10 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { d.Done.License = d.Lic.Valid d.DepoLicensed = moduleAllowed(h.rc, "quik-mnp") d.DepoDB = s.Fancy.DepoDB() - d.Done.Depo = strings.TrimSpace(s.Fancy.Server) != "" || !d.DepoLicensed + d.DepoTables = depoTablesView(s.Fancy) + d.DepoJob = depoJobView(s.Fancy) + // Шаг 3 выполнен, когда есть подключение, трансферные таблицы и работающая обработка (job). + d.Done.Depo = !d.DepoLicensed || (strings.TrimSpace(s.Fancy.Server) != "" && s.Fancy.DepoTablesCheckOK && s.Fancy.DepoJobCheckOK) d.Done.Postgres = s.Postgres.DSN != "" { ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) @@ -256,6 +264,19 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { step = 7 } d.Step = step + if step == 3 && d.DepoLicensed && strings.TrimSpace(s.Fancy.Server) != "" { + // Есть ли у сервера SQL Agent: у Express его нет, у PostgreSQL — тоже; тогда обработку + // запускает сам bj. Иначе по умолчанию джобу SQL Agent создаёт bj. + d.DepoJobDef = "bj" + if !s.Fancy.IsPostgres() { + ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) + d.DepoEdition, d.DepoAgent = depoServerEdition(ctx, s.Fancy) + cancel() + if d.DepoAgent || d.DepoEdition == "" { + d.DepoJobDef = "sql-auto" + } + } + } if step == 1 && d.Done.License { // Образ ставит ядро той версии, с которой собран; всё, что вышло позже (исправления ядра, // установщика и служб), приезжает обновлением — предлагаем его сразу после ввода ключа. @@ -268,6 +289,21 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { render(w, h.tpl.a.wizard, d) } +// depoServerEdition — редакция SQL Server и есть ли у неё SQL Agent (EngineEdition 4 — Express). +func depoServerEdition(ctx context.Context, f FancyMSSQLSettings) (string, bool) { + db, err := fancyOpen(f, "master") + if err != nil { + return "", false + } + defer db.Close() + var eng int + var ed string + if err := db.QueryRowContext(ctx, "SELECT CAST(SERVERPROPERTY('EngineEdition') AS int), CAST(SERVERPROPERTY('Edition') AS nvarchar(128))").Scan(&eng, &ed); err != nil { + return "", false + } + return ed, eng != 4 +} + // wizardProgressFile — номер последнего пройденного шага мастера (переход «Далее» на шаг N // означает, что шаги до N пройдены). const wizardProgressFile = "/var/lib/bj/.bj/wizard-progress" diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index 1f7662c..7c347c0 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -90,7 +90,9 @@ {{if not .DepoLicensed}}

Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается. После добавления модуля в лицензию он станет доступен здесь и в «Настройка → Обмен Fancy».

{{else}} - {{if .Done.Depo}}

✓ Подключение сохранено: {{.Settings.Fancy.Server}}, база {{.DepoDB}}. {{if .Settings.Fancy.DepoTablesCheckOK}}Трансферные таблицы на месте.{{else}}Трансферные таблицы ещё не проверены — «Настройка → Обмен Fancy → Трансферные таблицы DEPO».{{end}}

{{end}} +

Три части: подключение к базе, трансферные таблицы обмена, обработка (job), которая превращает записи bj в документы DEPO.

+ +

1. Подключение к базе DEPO {{if .Settings.Fancy.Server}}✓ {{.Settings.Fancy.Server}}{{end}}

@@ -102,6 +104,65 @@
{{if .Settings.Fancy.Server}}{{end}}
+ {{if .Settings.Fancy.Server}} + {{$btn2 := "background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)"}} +

2. Трансферные таблицы FTV_M2M {{if .Settings.Fancy.DepoTablesCheckOK}}✓ на месте{{end}}

+
Что это. Четыре таблицы в базе DEPO, через которые идёт обмен: поручения FTV_M2M и их строки, события FTV_M2M_EVENTS, справочник участников НРД. Данные учёта они не трогают; создание можно повторять — добавляется только недостающее.
+
+ + + + + +
+ {{if not .Settings.Fancy.DepoTablesCheckOK}} +
+ +

Создать таблицы в базе {{.DepoTables.DB}}. Логин администратора СУБД ({{if .DepoTables.Postgres}}владелец базы{{else}}db_owner в базе или sa{{end}}) нужен один раз и не сохраняется; bj заодно выдаст права своему логину {{.DepoTables.User}}. Пусто — попробовать логином bj.

+
+ + +
+
или скачать скрипт для администратора
+
+ {{end}} +
+ {{if .Settings.Fancy.DepoTablesCheck}}

{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}}

{{end}} + +

3. Обработка обмена (job) {{if .Settings.Fancy.DepoJobCheckOK}}✓ работает{{end}}

+
Что это. Процедура {{.DepoJob.Proc}} в базе DEPO каждые {{.DepoJob.Every}} с забирает записи bj из трансферных таблиц и создаёт по ним документы и операции DEPO. Кто-то должен её запускать.
+ {{if .DepoEdition}}Ваш сервер: {{.DepoEdition}} — {{if .DepoAgent}}есть SQL Server Agent, рекомендуем джобу Agent, её создаст bj.{{else}}редакция без SQL Server Agent, процедуру будет запускать сам bj.{{end}}{{else if .DepoTables.Postgres}}Ваш сервер: PostgreSQL — процедуру будет запускать сам bj.{{end}}
+ {{$mode := .Settings.Fancy.DepoJobMode}}{{if not $mode}}{{$mode = .DepoJobDef}}{{end}} +
+ + + + + {{if not .DepoTables.Postgres}}{{end}} + + {{if not .DepoTables.Postgres}}{{end}} +
+
+ {{if eq .Settings.Fancy.DepoJobMode "sql-auto"}} +
+ +

Создать джобу {{.DepoJob.Name}}. Логин администратора SQL (sysadmin, например sa) нужен один раз и не сохраняется. SQL Server Agent должен быть запущен.

+
+ + +
+
+
+ {{else if eq .Settings.Fancy.DepoJobMode "bj"}} +

Администратору SQL — выполнить один раз:

{{.DepoJob.GrantScript}}
+ {{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}} +

Скрипт для администратора SQL:

{{.DepoJob.AgentScript}}
+ {{end}} + {{if .Settings.Fancy.DepoJobMode}} +
bj кладёт служебное событие и ждёт до 40 с, пока его обработают
+ {{if .Settings.Fancy.DepoJobCheck}}

{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}}

{{end}} + {{end}} + {{end}} {{end}}
← К шагу 2