Заполнение портфеля ДЕПО: кнопка включить/выключить, выбор клиента DEPO (поиск) — «Залить сейчас» только ему или добавить в постоянное; именование (префикс счёта, шаблон договора ЛК, раздел, хранение) — в настройках
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
//go:build live
|
||||
|
||||
package lkgateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLKPortfolioClientsLive(t *testing.T) {
|
||||
rc, err := NewRuntimeConfig("/var/lib/bj/.bj/setup.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
list, err := lkPortfolioClients(context.Background(), rc, os.Getenv("Q"))
|
||||
fmt.Println(err)
|
||||
for _, c := range list {
|
||||
fmt.Printf("%+v\n", c)
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ func lkPaperType(cfi, name string) string {
|
||||
// (пусто — все). skipped — бумаги, которые не отправляются, с причиной.
|
||||
func lkPortfolioLines(ctx context.Context, rc *RuntimeConfig, depoPort string, onlyISIN map[string]bool) (lines []LKPortfolioLine, skipped []string, err error) {
|
||||
fs := rc.Snapshot().Fancy
|
||||
ps := rc.Snapshot().LK.PortfolioSync
|
||||
pos, err := depoClientPositions(ctx, fs, depoPort)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("DEPO: %w", err)
|
||||
@@ -106,7 +107,7 @@ func lkPortfolioLines(ctx context.Context, rc *RuntimeConfig, depoPort string, o
|
||||
}
|
||||
lines = append(lines, LKPortfolioLine{
|
||||
ISIN: isin, Name: strings.TrimSpace(p.SecurityName), RegNumber: strings.TrimSpace(reg), Count: p.Quantity,
|
||||
DepoAccount: p.AccountNum, Section: lkPortfolioSection, Storage: lkPortfolioStorage, PaperType: lkPaperType(cfi, p.SecurityName),
|
||||
DepoAccount: p.AccountNum, Section: ps.section(), Storage: ps.storage(), PaperType: lkPaperType(cfi, p.SecurityName),
|
||||
})
|
||||
}
|
||||
return lines, skipped, nil
|
||||
@@ -167,6 +168,24 @@ type LKPortfolioSyncSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
// Pairs — «договор депо в ЛК = портфель DEPO» через «;», напр. «ДД-816=БР-859».
|
||||
Pairs string `json:"pairs,omitempty"`
|
||||
// Именование у депозитариев разное — всё настраивается; пусто — как у Фонтвьель.
|
||||
AccountPrefix string `json:"account_prefix,omitempty"` // префикс торгового счёта в DEPO: «ВТНКЦ»
|
||||
ContractTemplate string `json:"contract_template,omitempty"` // номер договора депо в ЛК: «ДД-{n}», {n} — номер счёта без префикса и ведущих нулей
|
||||
Section string `json:"section,omitempty"` // раздел счёта депо в ЛК: «Основной»
|
||||
Storage string `json:"storage,omitempty"` // место хранения: «НРД»
|
||||
}
|
||||
|
||||
func (s LKPortfolioSyncSettings) accountPrefix() string {
|
||||
return pickStr(strings.TrimSpace(s.AccountPrefix), "ВТНКЦ")
|
||||
}
|
||||
func (s LKPortfolioSyncSettings) contractTemplate() string {
|
||||
return pickStr(strings.TrimSpace(s.ContractTemplate), "ДД-{n}")
|
||||
}
|
||||
func (s LKPortfolioSyncSettings) section() string {
|
||||
return pickStr(strings.TrimSpace(s.Section), lkPortfolioSection)
|
||||
}
|
||||
func (s LKPortfolioSyncSettings) storage() string {
|
||||
return pickStr(strings.TrimSpace(s.Storage), lkPortfolioStorage)
|
||||
}
|
||||
|
||||
type lkPortfolioPair struct{ LK, Depo string }
|
||||
@@ -263,24 +282,159 @@ func lkPortfolioStates() []LKPortfolioSyncState {
|
||||
|
||||
// registerLKPortfolioSync — POST /admin/setup/lk-portfolio: настройки и «синхронизировать сейчас».
|
||||
func registerLKPortfolioSync(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
// GET /admin/setup/lk-portfolio/clients?q= — поиск клиента DEPO для заливки портфеля.
|
||||
mux.HandleFunc("/admin/setup/lk-portfolio/clients", func(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := lkPortfolioClients(r.Context(), rc, r.URL.Query().Get("q"))
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if err != nil {
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(list)
|
||||
})
|
||||
mux.HandleFunc("/admin/setup/lk-portfolio", func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if r.FormValue("action") == "sync" {
|
||||
lk := rc.Snapshot().LK
|
||||
if r.Form.Has("pairs") {
|
||||
lk.PortfolioSync.Pairs = strings.TrimSpace(r.FormValue("pairs"))
|
||||
}
|
||||
if r.Form.Has("naming") { // именование у депозитария
|
||||
lk.PortfolioSync.AccountPrefix = strings.TrimSpace(r.FormValue("account_prefix"))
|
||||
lk.PortfolioSync.ContractTemplate = strings.TrimSpace(r.FormValue("contract_template"))
|
||||
lk.PortfolioSync.Section = strings.TrimSpace(r.FormValue("section"))
|
||||
lk.PortfolioSync.Storage = strings.TrimSpace(r.FormValue("storage"))
|
||||
}
|
||||
switch r.FormValue("action") {
|
||||
case "on":
|
||||
lk.PortfolioSync.Enabled = lk.PortfolioSync.Pairs != ""
|
||||
case "off":
|
||||
lk.PortfolioSync.Enabled = false
|
||||
}
|
||||
if err := rc.UpdateLK(lk); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// выбранный клиент: «Залить сейчас» (только его, для теста) или «Добавить в постоянное заполнение»
|
||||
if port, lkc := strings.TrimSpace(r.FormValue("client_port")), strings.TrimSpace(r.FormValue("client_lk")); port != "" && lkc != "" {
|
||||
pr := lkPortfolioPair{LK: lkc, Depo: port}
|
||||
if r.FormValue("action") == "client_add" {
|
||||
have := false
|
||||
for _, x := range lk.PortfolioSync.pairs() {
|
||||
if x.LK == lkc {
|
||||
have = true
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
lk.PortfolioSync.Pairs = strings.Trim(lk.PortfolioSync.Pairs+"; "+lkc+"="+port, "; ")
|
||||
_ = rc.UpdateLK(lk)
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
lkPortfolioPushPair(ctx, rc, pr)
|
||||
cancel()
|
||||
http.Redirect(w, r, "/admin/setup#lk-portfolio", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if a := r.FormValue("action"); a == "sync" || a == "on" { // включили или «Заполнить сейчас» — сразу отправить
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
lkPortfolioSyncTick(ctx, rc, true)
|
||||
cancel()
|
||||
} else {
|
||||
lk := rc.Snapshot().LK
|
||||
lk.PortfolioSync.Enabled = r.FormValue("enabled") != ""
|
||||
lk.PortfolioSync.Pairs = strings.TrimSpace(r.FormValue("pairs"))
|
||||
if err := rc.UpdateLK(lk); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/admin/setup#lk-portfolio", http.StatusSeeOther)
|
||||
})
|
||||
}
|
||||
|
||||
// LKPortfolioClient — клиент DEPO для выбора в настройках: портфель, ФИО, торговый счёт и договор ЛК.
|
||||
type LKPortfolioClient struct {
|
||||
Port string `json:"port"`
|
||||
Name string `json:"name"`
|
||||
Account string `json:"account"`
|
||||
LK string `json:"lk"`
|
||||
}
|
||||
|
||||
// lkContractByAccount — договор депо в ЛК по торговому счёту DEPO по шаблону: у Фонтвьель «ВТНКЦ00000816» →
|
||||
// «ДД-816» (проверено 29.09.2026 на ДД-816 и ДД-1515). Префикс и шаблон — в настройках.
|
||||
func lkContractByAccount(ps LKPortfolioSyncSettings, acc string) string {
|
||||
acc = strings.TrimSpace(acc)
|
||||
pre := ps.accountPrefix()
|
||||
if !strings.HasPrefix(acc, pre) {
|
||||
return ""
|
||||
}
|
||||
n := strings.TrimLeft(strings.TrimPrefix(acc, pre), "0")
|
||||
if n == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ReplaceAll(ps.contractTemplate(), "{n}", n)
|
||||
}
|
||||
|
||||
// lkPortfolioClients — поиск клиентов DEPO по ФИО или номеру портфеля (открытые торговые счета).
|
||||
func lkPortfolioClients(ctx context.Context, rc *RuntimeConfig, q string) ([]LKPortfolioClient, error) {
|
||||
q = strings.TrimSpace(q)
|
||||
if len([]rune(q)) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
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, 15*time.Second)
|
||||
defer cancel()
|
||||
rows, err := db.QueryContext(cctx, `SELECT TOP 20 ISNULL(p.PORT_NUM,''), ISNULL(f.NAME,''), c.NUMBER
|
||||
FROM OD_CLIENTS c JOIN OD_FACES f ON f.ID=c.FACE 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 (f.NAME LIKE @q OR p.PORT_NUM LIKE @q OR c.NUMBER LIKE @q)
|
||||
ORDER BY f.NAME`, sql.Named("q", "%"+likeEscape(q)+"%"), sql.Named("pre", likeEscape(ps.accountPrefix())+"%"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []LKPortfolioClient
|
||||
for rows.Next() {
|
||||
var c LKPortfolioClient
|
||||
if rows.Scan(&c.Port, &c.Name, &c.Account) == nil && c.Port != "" {
|
||||
c.LK = lkContractByAccount(ps, c.Account)
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// lkPortfolioPushPair — залить портфель одного клиента сейчас (тест / «Залить сейчас»).
|
||||
func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioPair) {
|
||||
lines, skipped, err := lkPortfolioLines(ctx, rc, pr.Depo, nil)
|
||||
lkPortfolioState.Lock()
|
||||
s := lkPortfolioState.m[pr.LK]
|
||||
if s == nil {
|
||||
s = &LKPortfolioSyncState{LK: pr.LK, Depo: pr.Depo}
|
||||
lkPortfolioState.m[pr.LK] = s
|
||||
}
|
||||
s.Depo, s.CheckedAt, s.Skipped = pr.Depo, time.Now(), skipped
|
||||
lkPortfolioState.Unlock()
|
||||
msg := ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
} else if code, resp, perr := lkPortfolioPush(ctx, rc, pr.LK, lines); perr != nil {
|
||||
msg = perr.Error()
|
||||
} else if code != 200 || !strings.Contains(resp, `"success":true`) {
|
||||
msg = fmt.Sprintf("HTTP %d: %s", code, resp)
|
||||
}
|
||||
lkPortfolioState.Lock()
|
||||
s.Err = msg
|
||||
if msg == "" {
|
||||
raw, _ := json.Marshal(lines)
|
||||
sum := sha256.Sum256(raw)
|
||||
s.hash, s.PushedAt, s.Lines = hex.EncodeToString(sum[:]), time.Now(), len(lines)
|
||||
}
|
||||
lkPortfolioState.Unlock()
|
||||
if msg != "" {
|
||||
log.Printf("lk-portfolio: %s ← %s: заливка не прошла: %s", pr.LK, pr.Depo, msg)
|
||||
} else {
|
||||
log.Printf("lk-portfolio: %s ← %s: портфель ДЕПО залит в ЛК (%d бумаг, пропущено %d)", pr.LK, pr.Depo, len(lines), len(skipped))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,17 @@ func TestLKPaperTypeAndPairs(t *testing.T) {
|
||||
t.Fatalf("%+v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLKContractByAccount(t *testing.T) {
|
||||
def := LKPortfolioSyncSettings{}
|
||||
if got := lkContractByAccount(def, "ВТНКЦ00000816"); got != "ДД-816" {
|
||||
t.Fatalf("по умолчанию: %q", got)
|
||||
}
|
||||
if got := lkContractByAccount(def, "В00000816"); got != "" {
|
||||
t.Fatalf("не торговый счёт: %q", got)
|
||||
}
|
||||
other := LKPortfolioSyncSettings{AccountPrefix: "TR-", ContractTemplate: "D/{n}/2024"}
|
||||
if got := lkContractByAccount(other, "TR-0042"); got != "D/42/2024" {
|
||||
t.Fatalf("другое именование: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,15 +637,71 @@
|
||||
<label style="display:flex;align-items:flex-start;gap:8px;font-weight:400;flex-basis:100%;margin-top:6px"><input type="checkbox" name="emulate_nsd" style="width:auto;margin-top:3px" {{if .Settings.LK.EmulateNSD}}checked{{end}} onchange="this.form.submit()"> <span><b>Имитация НРД</b> — заявки ЛК проходят сверку с DEPO, блокировку в QUIK и получают статус в ЛК, но в НРД не отправляются: ответ контрагента имитирует bj.{{if and (eq .Settings.LK.ActiveEnv "prod") (not .Settings.LK.EmulateNSD)}} <span style="color:var(--warn)">Сейчас включена принудительно: боевой ЛК при тестовом профиле ИШ ({{.Settings.NSD.Profile}}).</span>{{end}}</span></label>{{end}}
|
||||
</form>
|
||||
<div id="lk-portfolio" style="margin-top:14px;padding:12px 14px;border:1px solid var(--border);border-radius:10px">
|
||||
<b>Портфель ДЕПО → ЛК</b> {{if .Settings.LK.PortfolioSync.Enabled}}<span class="badge confirmed">включено</span>{{else}}<span class="badge draft">выключено</span>{{end}}
|
||||
<b>Заполнение портфеля ДЕПО бумагами из DEPO</b> {{if .Settings.LK.PortfolioSync.Enabled}}<span class="badge confirmed">включено</span>{{else}}<span class="badge draft">выключено</span>{{end}}
|
||||
<p class="muted" style="margin:6px 0 8px;font-size:13px">Каждые 10 секунд, в том же проходе, что и приём заявок, bj сверяет портфель клиента в DEPO и отправляет его в ЛК (<code>/portfolio/import</code>, портфель ДЕПО) — только если остатки изменились. Номер гос. регистрации — из экспорта QUIK, раздел счёта — «Основной», хранение — НРД. Бумаги без номера гос. регистрации не отправляются (ЛК отвергает весь импорт).</p>
|
||||
<form method="post" action="/admin/setup/lk-portfolio" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap">
|
||||
<label style="display:flex;align-items:center;gap:6px;font-weight:400"><input type="checkbox" name="enabled" style="width:auto"{{if .Settings.LK.PortfolioSync.Enabled}} checked{{end}}> включить</label>
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:260px">Договор ЛК = портфель DEPO (через «;»)
|
||||
<input name="pairs" value="{{.Settings.LK.PortfolioSync.Pairs}}" placeholder="ДД-816=БР-859"></label>
|
||||
<button class="btn" type="submit">Сохранить</button>
|
||||
<button class="btn" type="submit" name="action" value="sync">Синхронизировать сейчас</button>
|
||||
<form method="post" action="/admin/setup/lk-portfolio" style="margin:0 0 8px">
|
||||
{{if .Settings.LK.PortfolioSync.Enabled}}<button class="btn" type="submit" name="action" value="off" style="background:var(--card);color:var(--err);border:1px solid var(--err)">Выключить заполнение портфеля ДЕПО бумагами</button>
|
||||
<button class="btn" type="submit" name="action" value="sync">Заполнить сейчас</button>
|
||||
{{else}}<button class="btn btn-ok" type="submit" name="action" value="on" onclick="if(!this.form.pairs.value.trim()){alert('Укажите договор ЛК и портфель DEPO, например ДД-816=БР-859');return false}">Включить заполнение портфеля ДЕПО бумагами</button>{{end}}
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-top:8px">
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:260px">Каких клиентов заполнять: договор ЛК = портфель DEPO (через «;»)
|
||||
<input name="pairs" value="{{.Settings.LK.PortfolioSync.Pairs}}" placeholder="ДД-816=БР-859"></label>
|
||||
<button class="btn" type="submit" name="action" value="save">Сохранить список</button>
|
||||
</div>
|
||||
</form>
|
||||
<details style="margin:8px 0 0"><summary style="cursor:pointer;font-size:12.5px">Именование у депозитария (счета, договоры, раздел, хранение)</summary>
|
||||
<form method="post" action="/admin/setup/lk-portfolio" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-top:8px">
|
||||
<input type="hidden" name="naming" value="1">
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Префикс торгового счёта в DEPO
|
||||
<input name="account_prefix" value="{{.Settings.LK.PortfolioSync.AccountPrefix}}" placeholder="ВТНКЦ" style="width:120px"></label>
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Номер договора депо в ЛК ({n} — номер счёта)
|
||||
<input name="contract_template" value="{{.Settings.LK.PortfolioSync.ContractTemplate}}" placeholder="ДД-{n}" style="width:160px"></label>
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Раздел счёта
|
||||
<input name="section" value="{{.Settings.LK.PortfolioSync.Section}}" placeholder="Основной" style="width:130px"></label>
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Место хранения
|
||||
<input name="storage" value="{{.Settings.LK.PortfolioSync.Storage}}" placeholder="НРД" style="width:110px"></label>
|
||||
<button class="btn" type="submit" name="action" value="save">Сохранить</button>
|
||||
</form>
|
||||
<p class="muted" style="font-size:12px;margin:4px 0 0">Пусто — значение по умолчанию (серым). Пример: счёт ВТНКЦ00000816 и шаблон ДД-{n} → договор ДД-816.</p>
|
||||
</details>
|
||||
<form method="post" action="/admin/setup/lk-portfolio" id="lkpf-client" style="margin:10px 0 0;padding-top:10px;border-top:1px dashed var(--border)">
|
||||
<div style="font-size:13px;font-weight:600;margin-bottom:4px">Залить одному клиенту (для теста)</div>
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap">
|
||||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:320px">Клиент DEPO — ФИО, портфель или счёт
|
||||
<input id="lkpf-q" autocomplete="off" placeholder="например, Погонщикова или БР-859"></label>
|
||||
<input type="hidden" name="client_port" id="lkpf-port"><input type="hidden" name="client_lk" id="lkpf-lk">
|
||||
<button class="btn" type="submit" name="action" value="client_push" id="lkpf-push" disabled>Залить сейчас</button>
|
||||
<button class="btn" type="submit" name="action" value="client_add" id="lkpf-add" disabled>Добавить в постоянное заполнение</button>
|
||||
</div>
|
||||
<div id="lkpf-list" style="margin-top:6px;font-size:12.5px"></div>
|
||||
<div id="lkpf-sel" class="muted" style="margin-top:4px;font-size:12.5px"></div>
|
||||
</form>
|
||||
<script>
|
||||
(function(){
|
||||
var q=document.getElementById('lkpf-q'), list=document.getElementById('lkpf-list'), sel=document.getElementById('lkpf-sel'), t;
|
||||
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||||
function pick(c){
|
||||
document.getElementById('lkpf-port').value=c.port; document.getElementById('lkpf-lk').value=c.lk;
|
||||
var ok=!!c.lk; document.getElementById('lkpf-push').disabled=!ok; document.getElementById('lkpf-add').disabled=!ok;
|
||||
sel.innerHTML=ok?('Выбран: <b>'+esc(c.name)+'</b> · портфель DEPO '+esc(c.port)+' · счёт '+esc(c.account)+' → договор в ЛК <b>'+esc(c.lk)+'</b>'):'У клиента нет торгового счёта ВТНКЦ — договор в ЛК не определить';
|
||||
list.innerHTML='';
|
||||
}
|
||||
q.addEventListener('input',function(){
|
||||
clearTimeout(t); var v=q.value.trim(); if(v.length<2){list.innerHTML='';return}
|
||||
t=setTimeout(function(){
|
||||
fetch('/admin/setup/lk-portfolio/clients?q='+encodeURIComponent(v),{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
|
||||
if(d.error){list.innerHTML='<span style="color:var(--err)">'+esc(d.error)+'</span>';return}
|
||||
if(!d||!d.length){list.innerHTML='<span class="muted">не найдено</span>';return}
|
||||
list.innerHTML=''; d.forEach(function(c){
|
||||
var a=document.createElement('a'); a.href='#'; a.style.display='block'; a.style.padding='2px 0';
|
||||
a.innerHTML=esc(c.name)+' · '+esc(c.port)+' · '+esc(c.account)+(c.lk?' → '+esc(c.lk):'');
|
||||
a.onclick=function(e){e.preventDefault(); pick(c)}; list.appendChild(a);
|
||||
});
|
||||
}).catch(function(){list.innerHTML='<span style="color:var(--err)">поиск недоступен</span>'});
|
||||
},300);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{{range lkPortfolioStates}}<div class="muted" style="font-size:12.5px;margin-top:8px"><b>{{.LK}}</b> ← {{.Depo}}: {{if .Err}}<span style="color:var(--err)">ошибка — {{.Err}}</span>{{else if .PushedAt.IsZero}}сверено {{.CheckedAt.Format "15:04:05"}}, отправки ещё не было{{else}}отправлено {{.PushedAt.Format "02.01 15:04:05"}} ({{.Lines}} бумаг), сверено {{.CheckedAt.Format "15:04:05"}}{{end}}{{range .Skipped}}<br>пропущено: {{.}}{{end}}</div>{{end}}
|
||||
</div>
|
||||
<div id="lk-real-nsd" style="margin-top:14px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;{{if .Settings.LK.RealNSDTest.Enabled}}background:var(--warn-weak){{end}}">
|
||||
|
||||
Reference in New Issue
Block a user