03918b1e58
Проверено по шагам мастера, что bj берёт из DEPO сам: подготовка базы, префикс счетов (шаг 3), реквизиты (шаг 4), готовность (шаг 10), ручной ввод, подбор клиента для портфеля ЛК. - Реквизиты (Det01): счёт НРД ссылается на исходную версию лица, а ИНН НРД бывает только в новых (в исходной — ИНН НДЦ) — лицо по любой версии (ID или SELF_ID; ИНН НРД или MNEM «НДЦ»); номер счёта — и «счёт/раздел», не только «счёт/LEI/код/раздел». Счёт НРД — тот, с которым записан выбранный раздел (пара «раздел + счёт» — по ней процедуры находят место хранения), а не самый частый. Для MSSQL — только шире: прежнее находится. - Поиск по ФИО, договору и счёту — без учёта регистра, как в MSSQL (Cyrillic_General_CI_AS): в базе Fancy с кодировкой NONE регистр кириллицы различается — Firebird-варианты сравнивают в WIN1251 (подбор клиента для портфеля ЛК — запросы вынесены в fsql/lkportfolio.go, поиск и список договоров). - На копии базы: шаг 4 — счёт, код, разделы, ИНН и наименования организации; поиск «бр-1147», «бубликов» находит; FBLivePrepare — 103 из 103. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
198 lines
8.1 KiB
Go
198 lines
8.1 KiB
Go
package lkgateway
|
|
|
|
// Живая проверка ЛК, ручного ввода и сверки переводов на Fancy на Firebird: функции выполняются на копии базы
|
|
// клиента по договору с бумагами. Запуск:
|
|
// BJ_FB_TEST_DSN='firebird://SYSDBA:пароль@127.0.0.1:3050/путь?charset=WIN1251' BJ_FB_TEST_CONTRACT='БР-1147' \
|
|
// BJ_FB_TEST_ACC='Б' BJ_FB_TEST_PASSPORT='3453 289090' BJ_FB_TEST_FIO='Бубликов Павел Иванович' \
|
|
// go test -run FBLiveLK -v ./internal/lkgateway
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func fbLiveSettings(t *testing.T) FancyMSSQLSettings {
|
|
dsn := os.Getenv("BJ_FB_TEST_DSN")
|
|
if dsn == "" || os.Getenv("BJ_FB_TEST_CONTRACT") == "" {
|
|
t.Skip("BJ_FB_TEST_DSN / BJ_FB_TEST_CONTRACT не заданы")
|
|
}
|
|
u, err := url.Parse(dsn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pass, _ := u.User.Password()
|
|
port, _ := strconv.Atoi(u.Port())
|
|
return FancyMSSQLSettings{DBType: "firebird", Server: u.Hostname(), Port: port, User: u.User.Username(), Password: pass,
|
|
DepoDatabase: strings.TrimPrefix(u.Path, "/"), AccountPrefix: os.Getenv("BJ_FB_TEST_ACC")}
|
|
}
|
|
|
|
func fbLiveDB(t *testing.T, s FancyMSSQLSettings) *sql.DB {
|
|
db, err := fancyOpen(s, s.DepoDB())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
return db
|
|
}
|
|
|
|
func TestFBLiveLK(t *testing.T) {
|
|
s := fbLiveSettings(t)
|
|
rc, err := NewRuntimeConfig(t.TempDir() + "/setup.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := rc.UpdateFancy(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prev := globalRC
|
|
globalRC = rc
|
|
defer func() { globalRC = prev }()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
contract := os.Getenv("BJ_FB_TEST_CONTRACT")
|
|
last := time.Now()
|
|
check := func(what string, err error, n int, sample any) {
|
|
took := time.Since(last)
|
|
last = time.Now()
|
|
if took > 5*time.Second { // ЛК ждёт ответа: на MSSQL эти чтения — доли секунды
|
|
t.Errorf("%s: %v — медленно", what, took.Round(time.Millisecond))
|
|
}
|
|
if err != nil {
|
|
t.Errorf("%s: %v", what, err)
|
|
return
|
|
}
|
|
t.Logf("%s (%v): %d — %+v", what, took.Round(time.Millisecond), n, sample)
|
|
}
|
|
first := func(n int, v any) any {
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
name, ok := depoContractExists(ctx, s, contract)
|
|
t.Logf("depoContractExists: %v %q", ok, name)
|
|
if !ok {
|
|
t.Errorf("договор %s не найден", contract)
|
|
}
|
|
pos, err := depoClientPositions(ctx, s, contract)
|
|
check("depoClientPositions", err, len(pos), pos)
|
|
lst, err := depoListContracts(ctx, s, "", 5)
|
|
check("depoListContracts", err, len(lst), first(len(lst), lst))
|
|
fnd, err := depoFindContracts(ctx, s, contract[:2], 5)
|
|
check("depoFindContracts", err, len(fnd), fnd)
|
|
// поиск без учёта регистра, как в MSSQL: строчными буквами
|
|
low := strings.ToLower(contract)
|
|
fnd, err = depoFindContracts(ctx, s, low, 5)
|
|
check("depoFindContracts("+low+")", err, len(fnd), fnd)
|
|
lst, err = depoListContracts(ctx, s, low, 5)
|
|
check("depoListContracts("+low+")", err, len(lst), lst)
|
|
if len(fnd) == 0 || len(lst) == 0 {
|
|
t.Errorf("поиск договора строчными (%s) не нашёл", low)
|
|
}
|
|
t.Logf("depoSuggestContracts: %v", depoSuggestContracts(ctx, s))
|
|
|
|
ids, face, accs, err := depoClientIDs(ctx, s, contract)
|
|
check("depoClientIDs", err, len(ids), map[string]any{"face": face, "accounts": accs})
|
|
from, to := time.Now().AddDate(-1, 0, 0).Format("2006-01-02"), time.Now().Format("2006-01-02")
|
|
ops, err := lkOperations(ctx, s, contract, from, to, 50)
|
|
check("lkOperations", err, len(ops), first(len(ops), ops))
|
|
inc, tax, err := lkIncome(ctx, s, contract, from, to)
|
|
check("lkIncome", err, len(inc), map[string]any{"tax": tax})
|
|
|
|
if s.SpectreDatabase == "" { // архив ЛК — из базы брокерского учёта SPECTRE; без неё — понятная ошибка
|
|
_, err := lkArchivePortfolio(ctx, s, contract, time.Now())
|
|
t.Logf("lkArchivePortfolio без SPECTRE: %v", err)
|
|
last = time.Now()
|
|
} else {
|
|
ap, err := lkArchivePortfolio(ctx, s, contract, time.Now())
|
|
check("lkArchivePortfolio", err, 1, ap)
|
|
aops, err := lkArchiveOperations(ctx, s, contract, time.Now().AddDate(-1, 0, 0), time.Now())
|
|
check("lkArchiveOperations", err, len(aops), first(len(aops), aops))
|
|
}
|
|
|
|
var isins []string
|
|
var lkpos []LKPosition
|
|
for _, p := range pos {
|
|
isins = append(isins, p.ISIN)
|
|
lkpos = append(lkpos, LKPosition{ISIN: p.ISIN, Name: p.SecurityName, Quantity: p.Quantity})
|
|
}
|
|
ca, err := lkCorpActions(ctx, s, isins)
|
|
check("lkCorpActions", err, len(ca), first(len(ca), ca))
|
|
docs, err := lkReadyDocuments(ctx, s, contract)
|
|
check("lkReadyDocuments", err, len(docs), first(len(docs), docs))
|
|
up := lkUpcoming(ctx, s, lkpos, 400)
|
|
check("lkUpcoming", nil, len(up), first(len(up), up))
|
|
fx := lkFxAccounting(ctx, s)
|
|
check("lkFxAccounting", nil, len(fx), fx)
|
|
if len(fx) == 0 {
|
|
t.Error("lkFxAccounting: нет курсов валют")
|
|
}
|
|
for _, isin := range isins {
|
|
card, err := securityCard(ctx, rc, isin, nil)
|
|
check("securityCard "+isin, err, 1, card)
|
|
}
|
|
|
|
cur, cands, err := depoAccPrefixInfo(ctx, s)
|
|
check("depoAccPrefixInfo", err, cur, cands)
|
|
if req, err := depoDetectRequisites(ctx, s); err != nil {
|
|
t.Errorf("depoDetectRequisites: %v", err)
|
|
} else {
|
|
t.Logf("depoDetectRequisites: %+v", *req)
|
|
}
|
|
ver, upd, err := depoVersion(ctx, s)
|
|
t.Logf("depoVersion: %q %q %v", ver, upd, err)
|
|
rd := readyDepo(ctx, rc)
|
|
t.Logf("readyDepo (шаг 10): %s — %s", rd.State, rd.Detail)
|
|
|
|
fio := strings.Fields(os.Getenv("BJ_FB_TEST_FIO"))
|
|
pp := strings.Fields(os.Getenv("BJ_FB_TEST_PASSPORT"))
|
|
if len(fio) == 3 && len(pp) == 2 {
|
|
cl, err := lkPortfolioClients(ctx, rc, strings.ToLower(fio[0]))
|
|
check("lkPortfolioClients("+strings.ToLower(fio[0])+")", err, len(cl), cl)
|
|
if len(cl) == 0 {
|
|
t.Errorf("подбор клиента для портфеля ЛК строчными (%s) не нашёл", strings.ToLower(fio[0]))
|
|
}
|
|
pairs, err := lkPortfolioAllPairs(ctx, rc, fbLiveDB(t, s))
|
|
check("lkPortfolioAllPairs", err, len(pairs), pairs)
|
|
low, _ := manualDepoSearch(rc, strings.ToLower(fio[0]))
|
|
if len(low) == 0 {
|
|
t.Errorf("ручной поиск клиента строчными (%s) не нашёл", strings.ToLower(fio[0]))
|
|
}
|
|
faces, msg := manualDepoSearch(rc, fio[0])
|
|
t.Logf("manualDepoSearch(%s): %d %s — %+v", fio[0], len(faces), msg, first(len(faces), faces))
|
|
if len(faces) == 0 {
|
|
t.Errorf("manualDepoSearch: клиент %s не найден (%s)", fio[0], msg)
|
|
}
|
|
if len(pos) > 0 {
|
|
id, msg := manualDepoClientByAccount(rc, pos[0].AccountNum)
|
|
t.Logf("manualDepoClientByAccount(%s): %d %s", pos[0].AccountNum, id, msg)
|
|
mp, msg := manualDepoPositions(rc, id)
|
|
t.Logf("manualDepoPositions: %d %s — %+v", len(mp), msg, first(len(mp), mp))
|
|
}
|
|
var secs []IncomingSecurity
|
|
var outs []depoOutSec
|
|
for i, p := range pos {
|
|
ref := "FBREF" + strconv.Itoa(i)
|
|
secs = append(secs, IncomingSecurity{ReferenceID: ref, ISIN: p.ISIN, Quantity: "1"})
|
|
outs = append(outs, depoOutSec{ReferenceID: ref, ISIN: p.ISIN, Qty: "1"})
|
|
}
|
|
in := runDepoCheck(&IncomingTransfer{InvestorLast: fio[0], InvestorFirst: fio[1], InvestorMiddle: fio[2], DocSeries: pp[0], DocNumber: pp[1], Securities: secs})
|
|
t.Logf("runDepoCheck (зачисление): ran=%v err=%q investor=%q account=%q %s — %s", in.Ran, in.Err, in.Investor, in.Account, in.Recommend, in.Summary)
|
|
out := runDepoCheckOut(fio[0], fio[1], fio[2], pp[0], pp[1], outs, "", "")
|
|
t.Logf("runDepoCheckOut (списание): ran=%v err=%q investor=%q account=%q %s — %s", out.Ran, out.Err, out.Investor, out.Account, out.Recommend, out.Summary)
|
|
if in.Err != "" || out.Err != "" || in.Investor == "" || out.Investor == "" || in.Account == "" || out.Account == "" {
|
|
t.Errorf("сверка переводов: зачисление %q / списание %q, инвестор %q / %q", in.Err, out.Err, in.Investor, out.Investor)
|
|
}
|
|
code, contr, err := quikResolveClient(ctx, rc, pp[0], pp[1])
|
|
t.Logf("quikResolveClient: %q %q %v", code, contr, err)
|
|
}
|
|
}
|