diff --git a/internal/license/license.go b/internal/license/license.go index a412df6..1f145c7 100644 --- a/internal/license/license.go +++ b/internal/license/license.go @@ -53,8 +53,16 @@ type License struct { // пока действует договор (ContractUntil). У офлайн-ключей оба поля пустые. InstallID string `json:"install_id,omitempty"` ContractUntil time.Time `json:"contract_until,omitempty"` + + // DevUntil — режим разработки до указанного момента: сервер лицензий временно включает его + // установке, чтобы разработчики bj подключились к серверу клиента (тестовые разделы и + // инструменты). Указатель — чтобы у ключей без режима разработки подписанный JSON не менялся. + DevUntil *time.Time `json:"dev_until,omitempty"` } +// DevMode — действует ли режим разработки на момент now. +func (l *License) DevMode(now time.Time) bool { return l.DevUntil != nil && now.Before(*l.DevUntil) } + // Token — лицензия + подпись. Именно это вводит клиент (одна base64-строка // или JSON-файл). Формат: base64url(payload).base64url(sig) — компактно. type Token struct { diff --git a/internal/lkgateway/admin.go b/internal/lkgateway/admin.go index 8989ed0..ab4eec8 100644 --- a/internal/lkgateway/admin.go +++ b/internal/lkgateway/admin.go @@ -346,6 +346,7 @@ type page struct { IsMockMode bool // true если ИШ не настроен — bj-server в режиме эмуляции MockReason string // короткое описание почему mock NPR bool // модуль «НПР / УДС» включён и разрешён лицензией + DevMode bool // режим разработки (сервер лицензий): видны служебные разделы } // globalRC — ссылка на runtime-конфиг для template-funcs/page helpers. @@ -704,6 +705,7 @@ func nowPage(title, active string) page { p := page{Title: title, Active: active, Now: time.Now().Format("02.01.2006 15:04:05")} if globalRC != nil { p.NPR = nprAvailable(globalRC) + p.DevMode = devMode(globalRC) s := globalRC.Snapshot() switch { case s.NSD.IGWBaseURL == "": diff --git a/internal/lkgateway/advanced_gate.go b/internal/lkgateway/advanced_gate.go index 9c9f82a..21c36c3 100644 --- a/internal/lkgateway/advanced_gate.go +++ b/internal/lkgateway/advanced_gate.go @@ -37,6 +37,9 @@ func advancedUnlocked(r *http.Request) bool { if globalRC != nil && moduleAllowed(globalRC, "quik-mnp") { return true } + if !devMode(globalRC) { // пароль на модули — инструмент разработчиков + return false + } c, err := r.Cookie(advancedCookie) if err != nil { return false diff --git a/internal/lkgateway/auth.go b/internal/lkgateway/auth.go index 52556c8..977a81a 100644 --- a/internal/lkgateway/auth.go +++ b/internal/lkgateway/auth.go @@ -180,6 +180,9 @@ type User struct { } func (u *User) Can(perm string) bool { + if perm == "dev" { // режим разработки: включает сервер лицензий, внутри — права настройки + return u != nil && devMode(globalRC) && u.Can("settings") + } if u == nil || !permLicensed(perm) { return false } @@ -809,16 +812,30 @@ func authExempt(p string) bool { return true case strings.HasPrefix(p, "/api/"): return true - case p == "/admin/api/callback-url": // служебный вызов эмулятора ЛК - return true case strings.HasPrefix(p, "/admin/npr/static/"): return true } return false } +// devOnlyPath — служебные разделы разработчиков (тестирование, имитации, прогоны с роботом НРД): +// доступны только в режиме разработки. +func devOnlyPath(p string) bool { + for _, pre := range []string{"/admin/m2m-testing", "/admin/incoming/depo-sim", "/admin/setup/test-nsd", "/admin/setup/test-run", + "/admin/setup/lk-simulate", "/admin/setup/postgres/quick-start", "/admin/help/robot", "/admin/api/clients", "/admin/api/callback-url", + "/admin/setup/advanced-unlock", "/admin/setup/advanced-lock"} { + if p == pre || strings.HasPrefix(p, pre+"/") { + return true + } + } + return false +} + // requiredPerm — какое право нужно для пути и метода. func requiredPerm(p, method string) string { + if devOnlyPath(p) { + return "dev" + } switch { case strings.HasPrefix(p, "/admin/setup") || p == "/admin/wizard": return "settings" @@ -853,6 +870,10 @@ func clientIP(r *http.Request) string { func authMiddleware(rc *RuntimeConfig, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := r.URL.Path + if devOnlyPath(p) && !devMode(rc) { // вне режима разработки служебных разделов нет вовсе + http.NotFound(w, r) + return + } if authExempt(p) { next.ServeHTTP(w, r) return @@ -1272,6 +1293,9 @@ func renderFor(w http.ResponseWriter, t *template.Template, data any) error { t2.Funcs(template.FuncMap{ "curUser": func() *User { return u }, "can": func(perm string) bool { + if perm == "dev" { + return devMode(globalRC) && (authOpen() || u.Can("settings")) + } if authOpen() { return true } diff --git a/internal/lkgateway/auth_dev_test.go b/internal/lkgateway/auth_dev_test.go new file mode 100644 index 0000000..ed2bcac --- /dev/null +++ b/internal/lkgateway/auth_dev_test.go @@ -0,0 +1,32 @@ +package lkgateway + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +// Служебные разделы разработчиков вне режима разработки отвечают 404 — даже при открытом входе. +func TestDevOnlyPaths(t *testing.T) { + for _, p := range []string{"/admin/m2m-testing", "/admin/m2m-testing/preview", "/admin/setup/test-run", "/admin/api/callback-url", "/admin/help/robot"} { + if !devOnlyPath(p) || requiredPerm(p, http.MethodGet) != "dev" { + t.Errorf("%s должен быть служебным", p) + } + } + for _, p := range []string{"/admin/m2m-testingx", "/admin/incoming", "/admin/setup", "/admin/help"} { + if devOnlyPath(p) { + t.Errorf("%s не служебный", p) + } + } + rc, err := NewRuntimeConfig(t.TempDir() + "/setup.json") + if err != nil { + t.Fatal(err) + } + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }) + h := authMiddleware(rc, next) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/admin/m2m-testing", nil)) + if rec.Code != http.StatusNotFound { + t.Fatalf("вне режима разработки: %d", rec.Code) + } +} diff --git a/internal/lkgateway/licensecheck.go b/internal/lkgateway/licensecheck.go index d99a78a..94c1383 100644 --- a/internal/lkgateway/licensecheck.go +++ b/internal/lkgateway/licensecheck.go @@ -24,7 +24,8 @@ type LicenseStatus struct { ContractUntil time.Time DaysLeft int AllowsUpdates bool - Features []string // признаки (модули), разрешённые ключом + Features []string // признаки (модули), разрешённые ключом + DevUntil time.Time // режим разработки до (выдаёт сервер лицензий; нулевое — выключен) Message string } @@ -71,6 +72,22 @@ func modulesAllowed(rc *RuntimeConfig) map[string]bool { return m } +// devModeForced — режим разработки в тестах пакета. +var devModeForced bool + +// devMode — режим разработки: сервер лицензий временно открыл разработчикам bj тестовые разделы и +// служебные инструменты на этой установке. Без него у клиента их нет (адреса отвечают 404). +func devMode(rc *RuntimeConfig) bool { + if devModeForced { + return true + } + if rc == nil { + return false + } + st := licenseStatus(rc) + return st.Valid && time.Now().Before(st.DevUntil) +} + // moduleAllowed — разрешён ли модуль (лицензионный признак). План enterprise — все модули. func moduleAllowed(rc *RuntimeConfig, feature string) bool { st := licenseStatus(rc) @@ -127,6 +144,9 @@ func licenseStatus(rc *RuntimeConfig) LicenseStatus { st.DaysLeft = lic.DaysLeft(now) st.AllowsUpdates = lic.AllowsUpdates() st.Features = lic.Features + if lic.DevUntil != nil { + st.DevUntil = *lic.DevUntil + } st.Online = lic.InstallID != "" if lic.Product != "" && lic.Product != "bj-server" { st.Message = "ключ выпущен для другого продукта (" + lic.Product + ")" diff --git a/internal/lkgateway/licenseclient.go b/internal/lkgateway/licenseclient.go index 6285caa..16e2591 100644 --- a/internal/lkgateway/licenseclient.go +++ b/internal/lkgateway/licenseclient.go @@ -171,17 +171,21 @@ func LicenseRenew(ctx context.Context, rc *RuntimeConfig) error { return licenseAccept(rc, r.Token, "продлена") } -// licenseNeedsRenew — онлайн-ключ, и сутки без успешного продления. +// licenseNeedsRenew — онлайн-ключ и 15 минут без продления: так режим разработки, включённый или +// выключенный на сервере лицензий, доходит до установки быстро. После неудачи — не чаще раза в час. func licenseNeedsRenew(rc *RuntimeConfig) bool { s := rc.Snapshot().License lic, err := verifyLicenseToken(s.Key) if err != nil || lic.InstallID == "" { return false } - return time.Since(s.LastSync) >= 23*time.Hour + if !s.LastOK && !s.LastAttempt.IsZero() && time.Since(s.LastAttempt) < time.Hour && time.Since(s.LastSync) < 23*time.Hour { + return false + } + return time.Since(s.LastSync) >= 15*time.Minute } -// licenseLoop — раз в час предупреждает о скором окончании лицензии и проверяет, пора ли продлить +// licenseLoop — раз в 5 минут предупреждает о скором окончании лицензии и проверяет, пора ли продлить // ключ (продление — раз в сутки, при сбое — повтор через час). func (s *Server) licenseLoop(ctx context.Context) { ensureInstallID(s.rc) @@ -192,7 +196,7 @@ func (s *Server) licenseLoop(ctx context.Context) { return case <-time.After(wait): } - wait = time.Hour + wait = 5 * time.Minute licenseExpiryWarn(s.rc, time.Now()) if !licenseNeedsRenew(s.rc) { continue diff --git a/internal/lkgateway/lkconnector.go b/internal/lkgateway/lkconnector.go index 099d2a6..d8e46fa 100644 --- a/internal/lkgateway/lkconnector.go +++ b/internal/lkgateway/lkconnector.go @@ -506,7 +506,8 @@ func lkClaimToRequest(ctx context.Context, rc *RuntimeConfig, ep LKEndpoint, cl, // MC0012500000, а серия/номер ДУЛ переопределяются на сценарий (робот // читает сценарий из DocumentSeries). Прод-заявки этим маркером не // затрагиваются. Позволяет прогонять все сценарии через путь ЛК. - if sc := lkDetectRobotScenario(str(data["contragent_name"])); sc != "" { + // Только в режиме разработки: у клиентов маршрута к роботу нет. + if sc := lkDetectRobotScenario(str(data["contragent_name"])); sc != "" && devMode(rc) { doc := Document{DocumentType: "21", Series: sc, Number: robotDocumentNumbers[sc]} req.Investor.Document = doc req.InvestorDocumentOverride = &doc diff --git a/internal/lkgateway/lkemulate.go b/internal/lkgateway/lkemulate.go index 991387b..dd2bc0e 100644 --- a/internal/lkgateway/lkemulate.go +++ b/internal/lkgateway/lkemulate.go @@ -35,7 +35,7 @@ var lkEmuSender *mock.Sender // lkEmulateNSD — идут ли заявки ЛК через имитатор НРД и почему. func lkEmulateNSD(rc *RuntimeConfig) (bool, string) { s := rc.Snapshot() - if s.LK.EmulateNSD { + if s.LK.EmulateNSD && devMode(rc) { return true, "включена в настройках" } if s.LK.ActiveEnv == "prod" && strings.HasPrefix(strings.ToLower(s.NSD.Profile), "test") { diff --git a/internal/lkgateway/lkemulate_test.go b/internal/lkgateway/lkemulate_test.go index 7095abc..1c8678c 100644 --- a/internal/lkgateway/lkemulate_test.go +++ b/internal/lkgateway/lkemulate_test.go @@ -55,6 +55,11 @@ func TestLKEmulateNSD(t *testing.T) { } s.LK.EmulateNSD = true _ = rc.UpdateLK(s.LK) + if on, _ := lkEmulateNSD(rc); on { + t.Error("флаг в настройках без режима разработки не действует") + } + devModeForced = true + defer func() { devModeForced = false }() if on, why := lkEmulateNSD(rc); !on || why != "включена в настройках" { t.Errorf("флаг в настройках: %v %q", on, why) } diff --git a/internal/lkgateway/lktransfer.go b/internal/lkgateway/lktransfer.go index fba47e5..00be6f6 100644 --- a/internal/lkgateway/lktransfer.go +++ b/internal/lkgateway/lktransfer.go @@ -204,7 +204,8 @@ func (s *Service) CreateLKTransfer(ctx context.Context, rc *RuntimeConfig, u LKU // ТЕСТОВЫЙ КЛИЕНТ: если серия ДУЛ совпадает с кодом сценария робота НРД // (1111/2001/2002/3333), заявка адресуется роботу-тестировщику, а серия/номер // документа задают сценарий. Так из ЛК можно прогнать полный цикл с НРД. - if _, isRobot := robotScenarios[u.DocSeries]; isRobot { + // Только в режиме разработки: у реальных паспортов такие серии тоже встречаются. + if _, isRobot := robotScenarios[u.DocSeries]; isRobot && devMode(rc) { claim.ReceiverCodeOverride = NSDRobotCode claim.InvestorDocumentOverride = &doc } diff --git a/internal/lkgateway/m2mcodes.go b/internal/lkgateway/m2mcodes.go index 04ce4f3..fc8df41 100644 --- a/internal/lkgateway/m2mcodes.go +++ b/internal/lkgateway/m2mcodes.go @@ -31,7 +31,7 @@ var m2mRejectCatalog = []m2mCodeInfo{ {"M2M08", "Операция нарушит законодательство РФ", "Проведение перевода приведёт к нарушению закона: например, бумага предназначена только для квалифицированных инвесторов, а инвестор не квалифицирован."}, {"M2M09", "Отказ принимающей стороны (доп.)", - "Дополнительный код отказа зачисляющей стороны (используется в тест-сценариях)."}, + "Дополнительный код отказа зачисляющей стороны."}, } // m2mServiceCatalog — коды сервиса МОСТ (ошибки/информирование). Приходят в ответе @@ -107,9 +107,9 @@ var m2mCodeTexts = map[string]string{ "M2M04": "Инвестор не найден", "M2M05": "Зачисляющий депозитарий не оказывает услуги по учёту прав на ценную бумагу", "M2M06": "Приостановление операций по счёту депо зачисления", - "M2M07": "Отказ принимающей стороны (тест 07)", + "M2M07": "Отказ принимающей стороны", "M2M08": "Проведение операции приведёт к нарушению законодательства РФ", - "M2M09": "Отказ принимающей стороны (тест 09)", + "M2M09": "Отказ принимающей стороны (дополнительный код)", "M2M10": "Получено невалидное сообщение", "M2M14": "Отправитель не зарегистрирован в справочнике участников M2M", "M2M15": "Получатель (зачисляющий депозитарий) не зарегистрирован в справочнике участников M2M", // предположительно (пара к M2M14); уточнить по Спецификации ЭД diff --git a/internal/lkgateway/quikcycle.go b/internal/lkgateway/quikcycle.go index 21c5b0d..f787ca4 100644 --- a/internal/lkgateway/quikcycle.go +++ b/internal/lkgateway/quikcycle.go @@ -466,8 +466,14 @@ func quikResolveClient(ctx context.Context, rc *RuntimeConfig, ser, num string) // ---- высокоуровневые операции цикла ---- -// quikActive — цикл M2M в МНП работает: записи разрешены или включена имитация. -func quikActive(s QuikMNPSettings) bool { return s.WritesEnabled || s.Emulate } +// quikActive — цикл M2M в МНП работает: записи разрешены или включена имитация. Имитация — только в +// режиме разработки; если он закончился при включённой имитации, цикл стоит (в МНП не пишем). +func quikActive(s QuikMNPSettings) bool { + if s.Emulate { + return devMode(globalRC) + } + return s.WritesEnabled +} // quikEmulated — шаг цикла в режиме имитации: связка переходит в состояние state, в МНП ничего не // пишется (нет пользователя QUIK для блокировок и лимитов); события в DEPO — как при реальной работе. diff --git a/internal/lkgateway/quikdb_handlers.go b/internal/lkgateway/quikdb_handlers.go index 3cf4f4e..b90921e 100644 --- a/internal/lkgateway/quikdb_handlers.go +++ b/internal/lkgateway/quikdb_handlers.go @@ -86,7 +86,9 @@ func (h *setupHandlers) saveQuikCycle(w http.ResponseWriter, r *http.Request) { } s := h.rc.Snapshot().Quik s.WritesEnabled = r.FormValue("writes_enabled") != "" - s.Emulate = r.FormValue("emulate") != "" + if devMode(h.rc) { // флажок имитации есть только в режиме разработки + s.Emulate = r.FormValue("emulate") != "" + } s.AutoOut = r.FormValue("auto_out") != "" s.AutoIn = r.FormValue("auto_in") != "" s.PollTerminal = r.FormValue("poll_terminal") != "" diff --git a/internal/lkgateway/server.go b/internal/lkgateway/server.go index efd8f43..7ed01bf 100644 --- a/internal/lkgateway/server.go +++ b/internal/lkgateway/server.go @@ -249,6 +249,15 @@ func (s *Server) Run(ctx context.Context) error { // Монитор здоровья + systemd watchdog: следит за фоновыми горутинами, // поднимает алерт при зависании и «пинает» systemd, пока всё живо. healthRegister("poll-lk", time.Minute) + // Ключ продлеваем до запуска фоновых циклов: режим разработки (имитации и т.п.) должен быть + // известен им с первого прохода. + if licenseNeedsRenew(s.rc) { + lctx, cancel := context.WithTimeout(ctx, 15*time.Second) + if err := LicenseRenew(lctx, s.rc); err != nil { + log.Printf("lk-gateway: лицензия при запуске: %v", err) + } + cancel() + } go s.superviseHealth(ctx) // Данные модуля личного кабинета — схема lk в PostgreSQL bj. diff --git a/internal/lkgateway/service.go b/internal/lkgateway/service.go index 4d3c2c7..ddc94a8 100644 --- a/internal/lkgateway/service.go +++ b/internal/lkgateway/service.go @@ -522,7 +522,9 @@ func dealToView(d *m2mcore.Deal) ClaimView { out.LastCallback = &cb } // Самопроверка тестовой заявки роботу: серия документа = код сценария. - out.TestResult = buildTestResult(out.Investor.Document.Series, out.M2MDecision, out.M2MResponse) + if devMode(globalRC) { // сценарии робота — только в режиме разработки: такие серии бывают у реальных паспортов + out.TestResult = buildTestResult(out.Investor.Document.Series, out.M2MDecision, out.M2MResponse) + } return out } diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index f12aee3..50ea395 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -185,7 +185,7 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) { d.Done.Crypto = s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.ModulePath != "" d.Done.Certs = len(d.Certs) > 0 d.Done.NSD = s.NSD.IGWBaseURL != "" && s.NSD.Profile != "" - d.Done.TestRun = s.LastTest != nil + d.Done.TestRun = s.LastTest != nil || (!d.DevMode && d.Done.NSD) // вне режима разработки тестовой заявки нет // Определяем текущий шаг. step := 1 @@ -612,7 +612,7 @@ func (h *setupHandlers) saveLKIntegration(w http.ResponseWriter, r *http.Request if a := r.FormValue("active_env"); a == "test" || a == "prod" { s.ActiveEnv = a } - if r.FormValue("emulate_nsd_form") != "" { // флажок есть только в форме активного окружения + if r.FormValue("emulate_nsd_form") != "" && devMode(h.rc) { // флажок — в форме активного окружения, в режиме разработки s.EmulateNSD = r.FormValue("emulate_nsd") != "" } if err := h.rc.UpdateLK(s); err != nil { diff --git a/internal/lkgateway/web/templates/admin_help.html b/internal/lkgateway/web/templates/admin_help.html index eab567e..bc31f48 100644 --- a/internal/lkgateway/web/templates/admin_help.html +++ b/internal/lkgateway/web/templates/admin_help.html @@ -29,12 +29,12 @@

ИШ НРД (профили GUEST/TEST3/PROD), команда Fansy (ETL в staging), уведомления (e-mail, Yandex Messenger, Telegram), порядок согласования.

- + {{if .DevMode}}

Тестирование с роботом MOEX МОСТ →

Робот НРД на TEST3 (код MC0012500000), 4 тестовых сценария (отказ / принять все / частично / встречный перевод), управление через DocumentSeries и DocumentNumber, тестовые наборы депозитариев и кодов ошибок.

-
+ {{end}}

Архитектура обмена с НРД →

diff --git a/internal/lkgateway/web/templates/admin_keywizard.html b/internal/lkgateway/web/templates/admin_keywizard.html index 851057b..75ba593 100644 --- a/internal/lkgateway/web/templates/admin_keywizard.html +++ b/internal/lkgateway/web/templates/admin_keywizard.html @@ -36,10 +36,10 @@

Ключ установлен на флешку, справочник сертификатов сформирован, Валидата проверена.

{{if $s.Backup}}

Бэкап прежнего носителя: {{$s.Backup}}

{{end}}
-
+ {{if .DevMode}} -
+ {{else}}
→ Проверить подключения{{end}}
diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index 05e08e8..55fef08 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -18,7 +18,7 @@ - +{{if .DevMode}} {{end}}
@@ -59,7 +59,7 @@

PostgreSQL

Журнал сделок m2m-core и принимающая БД. Сейчас: {{if .Settings.Postgres.DSN}}подключено{{else}}in-memory (данные не сохраняются){{end}}.

- {{if not .Settings.Postgres.DSN}} + {{if and .DevMode (not .Settings.Postgres.DSN)}}

Подключить автоматически

Поднимем локальный PostgreSQL в контейнере, применим миграции и запишем DSN. Для дев-стенда. Для прода — укажите свой DSN ниже.

@@ -227,16 +227,16 @@
-

Автоматический цикл M2M в МНП{{if .Settings.Quik.Emulate}} имитация{{end}}

+

Автоматический цикл M2M в МНП{{if .Settings.Quik.Emulate}} {{if .DevMode}}имитация{{else}}остановлен{{end}}{{end}}

Что делает bj в Модуле неторговых поручений по ходу перевода: исходящий — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; входящий — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Проверено на боевом МНП 09.09.2026. Каждый шаг виден в карточке перевода и на странице QUIK МНП.

- {{end}} @@ -616,12 +616,12 @@ bj работает с ЛК через выбранное окружение. - - +{{if .DevMode}} + {{end}}
-
+{{if .DevMode}}

Симулятор заявки M2M

Пока подача через веб-форму у ЕСИА Финанс не готова — bj сам собирает заявку depo_m2m и отправляет её в API ЛК (POST /claims/) через активное окружение. Ответ показывается ниже; затем заявку ловим опросом в «Переводы». Поля предзаполнены рабочим тестовым договором.

@@ -657,7 +657,7 @@

Результат (сырой ответ API) появится вверху страницы. Если «Раздел счёта не доступен для быстрых переводов» — Финансы должны включить признак M2M на разделе счёта.

-
+
{{end}}

Забрать заявки из ЛК

@@ -702,7 +702,7 @@

Ответ придёт во входящие ИШ (~30-60с). Список появится в логе bj-server и в m2m-handbook.json.

-
+{{if .DevMode}}

Тестовый пакет роботу НРД

Робот MC0012500000 эмулирует вторую сторону перевода. Выберите сценарий — bj-server отправит эталонный запрос через ИШ, ответ придёт во входящие. Требуется настроенный ИШ + профиль Валидаты.

@@ -734,7 +734,7 @@
{{end}} -
+
{{end}} {{/* ============ ОПОВЕЩЕНИЯ ============ */}} @@ -961,7 +961,7 @@
- {{/* ============ ПЕРСПЕКТИВНЫЕ МОДУЛИ (под паролем) ============ */}} +{{if .DevMode}} {{/* ============ ПЕРСПЕКТИВНЫЕ МОДУЛИ (под паролем) ============ */}}

Перспективные модули

Модули для будущих профилей внедрения «автомат» и «полуавтомат» (когда используется собственный личный кабинет, а не ЕСИА Финанс). Пока не в проде — скрыты, чтобы не мешать текущей работе. В профиле «через Финансы» они не нужны: проверку и блокировку бумаг закрывает сам ЕСИА Финанс.

@@ -992,7 +992,7 @@
{{end}} - + {{end}}
diff --git a/internal/lkgateway/web/templates/admin_wizard.html b/internal/lkgateway/web/templates/admin_wizard.html index af55c40..04c477e 100644 --- a/internal/lkgateway/web/templates/admin_wizard.html +++ b/internal/lkgateway/web/templates/admin_wizard.html @@ -47,7 +47,7 @@ Что выбрать? Если у вас уже есть рабочий PostgreSQL — нажмите «У меня уже есть PostgreSQL» и введите DSN. Если впервые настраиваете — выберите «Поднять автоматически», система сама развернёт контейнер с PostgreSQL и накатит миграции. - {{if not .Settings.Postgres.DSN}} + {{if and .DevMode (not .Settings.Postgres.DSN)}}

Вариант А — для тех, у кого нет своего PostgreSQL

Bridge-and-Join-s сам поднимет PostgreSQL в контейнере (podman-compose), создаст БД bj и накатит миграции. Подходит для дев-стенда. Для продакшена лучше указать свой DSN.

diff --git a/internal/lkgateway/web/templates/layout.html b/internal/lkgateway/web/templates/layout.html index 7a94f48..56e0117 100644 --- a/internal/lkgateway/web/templates/layout.html +++ b/internal/lkgateway/web/templates/layout.html @@ -287,7 +287,7 @@ label { font-size:13px; font-weight:500; } Входящие - {{end}}{{if can "operate"}}Тестирование M2M{{end}} + {{end}}{{if and .DevMode (can "operate")}}Тестирование M2M{{end}} {{if and .NPR (can "npr")}}НПР / УДС{{end}} {{if or (can "view") (can "quik")}} Обмен ▾