From bd1fe202f77bb388fe9d671e93c130bd24fd9385 Mon Sep 17 00:00:00 2001 From: fontvielle Date: Wed, 21 Oct 2026 17:24:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D1=80=D1=82=D1=84=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=94=D0=95=D0=9F=D0=9E=20=D0=B2=20=D0=9B=D0=9A:=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=C2=AB=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=C2=BB=20+=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Кнопка «Включить для всех пользователей» с предупреждением: заполнение портфеля запускается для ВСЕХ открытых торговых счетов DEPO (по префиксу), договор ЛК выводится из счёта по шаблону — так же как в тесте с одним клиентом, только по торговым счетам. Поле AllClients; tick берёт пары из lkPortfolioAllPairs. Прежний режим «по списку» (Pairs) сохранён. - Поля именования (префикс счёта, шаблон договора, раздел, место хранения) предзаполняются из базы DEPO (префикс — depoAccPrefix) и значений по умолчанию, остаются редактируемыми. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn --- deploy/release/component-versions.json | 4 +- internal/lkgateway/lkportfolio.go | 60 +++++++++++++++++-- .../lkgateway/web/templates/admin_setup.html | 16 ++--- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/deploy/release/component-versions.json b/deploy/release/component-versions.json index db57a02..1cac9d0 100644 --- a/deploy/release/component-versions.json +++ b/deploy/release/component-versions.json @@ -20,8 +20,8 @@ "version": "2026.10.21.6" }, "bj-server": { - "fingerprint": "e8cd8c67b83a419297ca4cbcf5b488f11b8776a032908eca08f0df254b7c0488", - "version": "2026.10.21.9" + "fingerprint": "6cf5639b24a45d8c39e1896ab11301d00de8eb4d40f4364be70c31fb2b5bb6df", + "version": "2026.10.21.10" }, "crypto-service.jar": { "fingerprint": "2c4fd536bc9b1c20c69a70e8dcd374a61847b263aeb2e474c45debb01fbd8233", diff --git a/internal/lkgateway/lkportfolio.go b/internal/lkgateway/lkportfolio.go index ac6961c..405164f 100644 --- a/internal/lkgateway/lkportfolio.go +++ b/internal/lkgateway/lkportfolio.go @@ -173,6 +173,9 @@ type LKPortfolioSyncSettings struct { ContractTemplate string `json:"contract_template,omitempty"` // номер договора депо в ЛК: «ДД-{n}», {n} — номер счёта без префикса и ведущих нулей Section string `json:"section,omitempty"` // раздел счёта депо в ЛК: «Основной» Storage string `json:"storage,omitempty"` // место хранения: «НРД» + // AllClients — заполнять портфель ВСЕМ клиентам: все открытые торговые счета DEPO + // (по префиксу), договор ЛК выводится из счёта по шаблону. Иначе — только Pairs. + AllClients bool `json:"all_clients,omitempty"` } func (s LKPortfolioSyncSettings) accountPrefix() string { @@ -223,7 +226,16 @@ func lkPortfolioSyncTick(ctx context.Context, rc *RuntimeConfig, force bool) { if !st.Enabled { return } - for _, pr := range st.pairs() { + prs := st.pairs() + if st.AllClients { + all, err := lkPortfolioAllPairs(ctx, rc) + if err != nil { + log.Printf("lk-portfolio: список клиентов (все): %v", err) + return + } + prs = all + } + for _, pr := range prs { lines, skipped, err := lkPortfolioLines(ctx, rc, pr.Depo, nil) lkPortfolioState.Lock() s := lkPortfolioState.m[pr.LK] @@ -310,9 +322,11 @@ func registerLKPortfolioSync(mux *http.ServeMux, rc *RuntimeConfig) { } switch r.FormValue("action") { case "on": - lk.PortfolioSync.Enabled = lk.PortfolioSync.Pairs != "" + lk.PortfolioSync.Enabled = lk.PortfolioSync.Pairs != "" || lk.PortfolioSync.AllClients + case "on_all": + lk.PortfolioSync.AllClients, lk.PortfolioSync.Enabled = true, true case "off": - lk.PortfolioSync.Enabled = false + lk.PortfolioSync.Enabled, lk.PortfolioSync.AllClients = false, false } if err := rc.UpdateLK(lk); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -339,7 +353,7 @@ func registerLKPortfolioSync(mux *http.ServeMux, rc *RuntimeConfig) { http.Redirect(w, r, "/admin/setup#lk-portfolio", http.StatusSeeOther) return } - if a := r.FormValue("action"); a == "sync" || a == "on" { // включили или «Заполнить сейчас» — сразу отправить + if a := r.FormValue("action"); a == "sync" || a == "on" || a == "on_all" { // включили или «Заполнить сейчас» — сразу отправить ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) lkPortfolioSyncTick(ctx, rc, true) cancel() @@ -405,6 +419,44 @@ func lkPortfolioClients(ctx context.Context, rc *RuntimeConfig, q string) ([]LKP return out, rows.Err() } +// lkPortfolioAllPairs — все открытые торговые счета DEPO с портфелем (режим «для всех +// клиентов»): фильтр по префиксу счёта, договор ЛК выводится из счёта по шаблону, +// портфель — PORT_NUM. Только MSSQL-база DEPO (как и поиск одного клиента). +func lkPortfolioAllPairs(ctx context.Context, rc *RuntimeConfig) ([]lkPortfolioPair, error) { + fs := rc.Snapshot().Fancy + ps := rc.Snapshot().LK.PortfolioSync + db, err := fancyOpen(fs, fs.DepoDB()) + if err != nil { + return nil, err + } + defer db.Close() + cctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + rows, err := db.QueryContext(cctx, `SELECT ISNULL(p.PORT_NUM,''), c.NUMBER + FROM OD_CLIENTS c LEFT JOIN OD_CLIENTS_PORTS p ON p.CLIENT=c.ID + WHERE c.NUMBER LIKE @pre AND c.STATE=0 AND c.D_CLOSE IS NULL AND p.PORT_NUM IS NOT NULL`, + sql.Named("pre", likeEscape(ps.accountPrefix())+"%")) + if err != nil { + return nil, err + } + defer rows.Close() + var out []lkPortfolioPair + seen := map[string]bool{} + for rows.Next() { + var port, acc string + if rows.Scan(&port, &acc) != nil || strings.TrimSpace(port) == "" { + continue + } + lk := lkContractByAccount(ps, acc) + if lk == "" || seen[lk] { + continue + } + seen[lk] = true + out = append(out, lkPortfolioPair{LK: lk, Depo: strings.TrimSpace(port)}) + } + return out, rows.Err() +} + // lkPortfolioPushPair — залить портфель одного клиента сейчас (тест / «Залить сейчас»). func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioPair) { lines, skipped, err := lkPortfolioLines(ctx, rc, pr.Depo, nil) diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index fc5f8f8..1024cef 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -681,12 +681,14 @@ {{end}}
- Заполнение портфеля ДЕПО бумагами из DEPO {{if .Settings.LK.PortfolioSync.Enabled}}включено{{else}}выключено{{end}} + Заполнение портфеля ДЕПО бумагами из DEPO {{if .Settings.LK.PortfolioSync.Enabled}}включено{{if .Settings.LK.PortfolioSync.AllClients}} — все клиенты{{else}} — по списку{{end}}{{else}}выключено{{end}}

Каждые 10 секунд, в том же проходе, что и приём заявок, bj сверяет портфель клиента в DEPO и отправляет его в ЛК (/portfolio/import, портфель ДЕПО) — только если остатки изменились. Номер гос. регистрации — из экспорта QUIK, раздел счёта — «Основной», хранение — НРД. Бумаги без номера гос. регистрации не отправляются (ЛК отвергает весь импорт).

{{if .Settings.LK.PortfolioSync.Enabled}} - {{else}}{{end}} + {{if not .Settings.LK.PortfolioSync.AllClients}}{{end}} + {{else}} + {{end}}
@@ -697,16 +699,16 @@ + + + + -

Пусто — значение по умолчанию (серым). Пример: счёт ВТНКЦ00000816 и шаблон ДД-{n} → договор ДД-816.

+

Заполнено из базы DEPO (префикс счёта) и значений по умолчанию — можно изменить. Пример: счёт ВТНКЦ00000816 и шаблон ДД-{n} → договор ДД-816.

Залить одному клиенту (для теста)