package lkgateway // lkclient.go — как один и тот же клиент называется в разных системах. // // В учётных записях кабинета клиент задан номером договора, как в DEPO: «БР-1545», «БРИИС-514». // В экспорте QUIK тот же клиент — код латиницей без дефиса: BR1545, BRIIS514; внебиржевой // раздел — с суффиксом OUT. В SPECTRE договор — строка D_B_CONTRACTS.NAME вида «БР-1545 ФИО». import ( "context" "database/sql" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql" "strings" "sync" "time" ) // lkContractNumber — номер договора без ФИО: «БР-1545 Иванов Иван» → «БР-1545». func lkContractNumber(contract string) string { c := strings.TrimSpace(contract) if i := strings.IndexByte(c, ' '); i > 0 { c = c[:i] } return strings.ToUpper(c) } // lkQuikCodes — коды клиента в QUIK по номеру договора: основной и внебиржевой раздел. func lkQuikCodes(contract string) []string { num := lkContractNumber(contract) if num == "" { return nil } repl := strings.NewReplacer("-", "", "А", "A", "Б", "B", "В", "V", "Д", "D", "И", "I", "С", "S", "Р", "R", "У", "U", "Т", "T", "К", "K", "М", "M", "Н", "N", "О", "O", "Е", "E", "П", "P", "Ф", "F", "Ц", "C") code := repl.Replace(num) return []string{code, code + "OUT"} } // spectreContract — договор клиента в SPECTRE. type spectreContract struct { DOC int64 Investor int64 Name string Open time.Time Closed bool } var spectreContractCache = struct { mu sync.Mutex m map[string]spectreContract at map[string]time.Time }{m: map[string]spectreContract{}, at: map[string]time.Time{}} // spectreOpen — подключение к копии SPECTRE тем же логином, что и к DEPO. func spectreOpen(s FancyMSSQLSettings) (*sql.DB, error) { db := strings.TrimSpace(s.SpectreDB()) return fancyOpen(s, db) } // SpectreDB — база SPECTRE рядом с DEPO: по умолчанию BJ_SPECTRE_DATA. func (s FancyMSSQLSettings) SpectreDB() string { if v := strings.TrimSpace(s.SpectreDatabase); v != "" { return v } return "BJ_SPECTRE_DATA" } // spectreContractFor — договор SPECTRE по номеру договора клиента (кэш 10 минут). func spectreContractFor(ctx context.Context, s FancyMSSQLSettings, contract string) (spectreContract, bool) { num := lkContractNumber(contract) if num == "" || strings.TrimSpace(s.Server) == "" { return spectreContract{}, false } spectreContractCache.mu.Lock() if c, ok := spectreContractCache.m[num]; ok && time.Since(spectreContractCache.at[num]) < 10*time.Minute { spectreContractCache.mu.Unlock() return c, c.DOC != 0 } spectreContractCache.mu.Unlock() conn, err := spectreOpen(s) if err != nil { return spectreContract{}, false } defer conn.Close() cctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() var c spectreContract var open, closed sql.NullTime var state sql.NullInt64 err = conn.QueryRowContext(cctx, fsql.Cl01, sql.Named("n", num)). Scan(&c.DOC, &c.Investor, &c.Name, &open, &closed, &state) if err == nil { c.Name = strings.TrimSpace(c.Name) if open.Valid { c.Open = open.Time } c.Closed = closed.Valid && closed.Time.Year() < 9000 && closed.Time.Before(time.Now()) } spectreContractCache.mu.Lock() spectreContractCache.m[num], spectreContractCache.at[num] = c, time.Now() spectreContractCache.mu.Unlock() return c, err == nil && c.DOC != 0 } // lkClientName — ФИО клиента для показа: из учётной записи, иначе из номера договора, // где учётная система хранит номер вместе с ФИО («БР-719 Аминов Вячеслав Маркович»). func lkClientName(u LKUser) string { if n := strings.TrimSpace(u.FullName); n != "" { return n } c := strings.TrimSpace(u.Contract) if i := strings.IndexByte(c, ' '); i > 0 { return strings.TrimSpace(c[i:]) } return "" }