feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
//go:build live
|
||||
|
||||
package lkgateway
|
||||
|
||||
// Разбор, какие данные пришли из боевого ЛК по заявке и как клиент сопоставлен с DEPO.
|
||||
// Только чтение: GET из ЛК, SELECT из DEPO. Паспорт и ФИО в выводе маскируются.
|
||||
// go test -c -tags live -o lkmatch.test ./internal/lkgateway/ && sudo ./lkmatch.test -test.run TestLKMatchLive -test.v
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mask(s string) string {
|
||||
r := []rune(strings.TrimSpace(s))
|
||||
switch {
|
||||
case len(r) == 0:
|
||||
return "(пусто)"
|
||||
case len(r) <= 2:
|
||||
return strings.Repeat("•", len(r))
|
||||
}
|
||||
return string(r[:1]) + strings.Repeat("•", len(r)-2) + string(r[len(r)-1:])
|
||||
}
|
||||
|
||||
func TestLKMatchLive(t *testing.T) {
|
||||
rc, err := NewRuntimeConfig("/var/lib/bj/.bj/setup.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
globalRC = rc
|
||||
ep, ok := lkActiveEndpoint(rc)
|
||||
if !ok {
|
||||
t.Skip("ЛК не настроен")
|
||||
}
|
||||
id := 69753
|
||||
if v := os.Getenv("CLAIM"); v != "" {
|
||||
id, _ = strconv.Atoi(v)
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, body, err := lkGet(ctx, ep, "/claims/"+strconv.Itoa(id))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var wrap struct {
|
||||
Claim map[string]any `json:"claim"`
|
||||
}
|
||||
_ = json.Unmarshal(body, &wrap)
|
||||
cl := wrap.Claim
|
||||
data, _ := cl["data"].(map[string]any)
|
||||
fmt.Printf("== Заявка ЛК #%d: kind=%v, user_id=%v, owner=%v/%v\n", id, cl["kind"], cl["user_id"], cl["owner_type"], cl["owner_id"])
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
b, _ := json.Marshal(data[k])
|
||||
fmt.Printf(" data.%s = %s\n", k, b)
|
||||
}
|
||||
|
||||
uid := toInt(cl["user_id"])
|
||||
_, ub, _ := lkGet(ctx, ep, "/users/"+strconv.Itoa(uid))
|
||||
var uw map[string]any
|
||||
_ = json.Unmarshal(ub, &uw)
|
||||
u := pickMap(uw, "user", "data")
|
||||
if u == nil {
|
||||
u = uw
|
||||
}
|
||||
fmt.Printf("== Пользователь ЛК #%d: поля карточки (значения ФИО и документа замаскированы)\n", uid)
|
||||
ukeys := make([]string, 0, len(u))
|
||||
for k := range u {
|
||||
ukeys = append(ukeys, k)
|
||||
}
|
||||
sort.Strings(ukeys)
|
||||
var present []string
|
||||
for _, k := range ukeys {
|
||||
if s := str(u[k]); s != "" && s != "<nil>" {
|
||||
present = append(present, k)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" заполненные поля: %s\n", strings.Join(present, ", "))
|
||||
for _, k := range []string{"surname", "name", "patronymic", "document_type", "pass_series", "pass_number", "document_series", "document_number", "birth_date", "inn", "snils"} {
|
||||
if v, ok := u[k]; ok {
|
||||
val := str(v)
|
||||
if k != "document_type" {
|
||||
val = mask(val)
|
||||
}
|
||||
fmt.Printf(" %s = %s\n", k, val)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := lkClaimToRequest(ctx, rc, ep, cl, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
inv := req.Investor
|
||||
fmt.Printf("== В сверку с DEPO передано: ФИО %s %s %s, документ тип %s серия %s номер %s\n",
|
||||
mask(inv.LastName), mask(inv.FirstName), mask(inv.MiddleName), inv.Document.DocumentType, mask(inv.Document.Series), mask(inv.Document.Number))
|
||||
var secs []depoOutSec
|
||||
acc, section := "", ""
|
||||
for _, s := range req.Securities {
|
||||
secs = append(secs, depoOutSec{ISIN: s.SecurityDetails.ISIN, Code: s.SecurityCode, Qty: strconv.FormatUint(s.Quantity.Whole, 10)})
|
||||
if len(s.SettlementAccounts) > 0 {
|
||||
acc, section = s.SettlementAccounts[0].SettlementLocation.AccountID, s.SettlementAccounts[0].SettlementLocation.SectionID
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("ACCOUNT"); v != "" {
|
||||
req.ClientAccount = v // проверка несовпадения: подставить чужой счёт
|
||||
}
|
||||
fmt.Printf("== Счёт из заявки: %s\n", req.ClientAccount)
|
||||
if v := os.Getenv("IIS_ACCOUNT"); v != "" { // признак ИИС по произвольному счёту
|
||||
fs := rc.Snapshot().Fancy
|
||||
if db, err := fancyOpen(fs, fs.DepoDB()); err == nil {
|
||||
var cid int64
|
||||
_ = db.QueryRowContext(ctx, "SELECT TOP 1 ID FROM OD_CLIENTS WHERE NUMBER=@p1", sql.Named("p1", v)).Scan(&cid)
|
||||
ok, det := depoAccountIIS(ctx, db, fs, cid)
|
||||
fmt.Printf("== ИИС по счёту %s: не ИИС=%v — %s\n", v, ok, det)
|
||||
db.Close()
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("FIO_LAST"); v != "" {
|
||||
inv.LastName = v // проверка расхождения ФИО
|
||||
}
|
||||
if v := os.Getenv("BIRTH"); v != "" {
|
||||
inv.BirthDate = v // проверка расхождения даты рождения
|
||||
}
|
||||
res := runDepoCheckOutAccount(inv.LastName, inv.FirstName, inv.MiddleName, inv.Document.Series, inv.Document.Number, inv.BirthDate, secs, acc, section, req.ClientAccount)
|
||||
fmt.Printf("== Сверка с DEPO: итог %s; лицо в DEPO: %s; счёт: %s\n", res.Summary, mask(res.Investor), res.Account)
|
||||
for _, s := range res.Secs {
|
||||
for _, c := range s.Checks {
|
||||
mark := "✓"
|
||||
if !c.OK {
|
||||
mark = "✗"
|
||||
} else if c.Warn {
|
||||
mark = "!"
|
||||
}
|
||||
fmt.Printf(" %s %s [%s]: %s\n", mark, c.Name, c.Code, c.Detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user