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:
zuevav
2026-09-15 19:41:12 +03:00
parent 59fc774f08
commit da12a9ec1c
132 changed files with 29969 additions and 1089 deletions
+128
View File
@@ -0,0 +1,128 @@
//go:build live
package lkgateway
// Живая проверка создания трансферных таблиц DEPO.
// PostgreSQL: локальный сервер (fancy_user/fancy_test), тестовая база создаётся и удаляется.
// MSSQL: база DEPO из настроек bj — только разбор DDL (SET NOEXEC ON) и проверка, без изменений.
// go test -tags live -run TestDepoTablesLive ./internal/lkgateway/ ; MSSQL — от bj (читает setup.json).
import (
"context"
"os"
"testing"
"time"
)
// BJ_PG_ADMIN/BJ_PG_ADMIN_PASS — роль с CREATEDB (создаёт базу и таблицы, выдаёт права);
// BJ_PG_USER/BJ_PG_PASS — «логин bj» без прав: проверка идёт от его имени.
func TestDepoTablesLivePostgres(t *testing.T) {
adminUser, adminPass := os.Getenv("BJ_PG_ADMIN"), os.Getenv("BJ_PG_ADMIN_PASS")
if adminUser == "" || os.Getenv("BJ_PG_USER") == "" {
t.Skip("не заданы BJ_PG_ADMIN / BJ_PG_USER")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
f := FancyMSSQLSettings{DBType: "postgres", Server: "localhost", Port: 5432, User: os.Getenv("BJ_PG_USER"), Password: os.Getenv("BJ_PG_PASS"), DepoDatabase: "bj_depo_tables_test"}
admin := f
admin.User, admin.Password = adminUser, adminPass
if conn, err := fancyOpen(admin, ""); err != nil {
t.Fatal(err)
} else {
_, err = conn.ExecContext(ctx, `CREATE DATABASE bj_depo_tables_test`)
conn.Close()
if err != nil {
t.Fatalf("создание тестовой базы: %v", err)
}
}
defer func() {
conn, _ := fancyOpen(admin, "")
defer conn.Close()
if _, err := conn.ExecContext(context.Background(), `DROP DATABASE IF EXISTS bj_depo_tables_test`); err != nil {
t.Logf("удаление тестовой базы: %v", err)
}
}()
if msg, ok := depoTablesCheck(ctx, f); ok {
t.Fatalf("до создания проверка должна падать: %s", msg)
} else {
t.Logf("до создания: %s", msg)
}
for i := 1; i <= 2; i++ {
msg, err := depoTablesCreate(ctx, f, adminUser, adminPass)
if err != nil {
t.Fatalf("создание, проход %d: %v", i, err)
}
t.Logf("проход %d: %s", i, msg)
}
msg, ok := depoTablesCheck(ctx, f)
t.Logf("проверка: %v %s", ok, msg)
if !ok {
t.Fatal("проверка не прошла")
}
db, err := fancyOpen(f, f.DepoDB())
if err != nil {
t.Fatal(err)
}
defer db.Close()
g := "5325b860-3265-49b6-986d-fa84fb41fe27"
for _, q := range []string{
`INSERT INTO FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, COMMENT) VALUES ('` + g + `', 'IN', 'MC0034900000', 'Погонщикова Виктория Олеговна', '4500 000000', 'тест')`,
`INSERT INTO FTV_M2M_LINES (GUID, REFERENCE, ISIN, SEC_CODE, SHARE, AMOUNT, PLACE_NUMBER, LINK_NUMBER) VALUES ('` + g + `', 'M2MTEST0001', 'RU000A107UL4', 'RU000A107UL4', 18824, 1.5, '36MC0413600000F00', '')`,
`INSERT INTO FTV_M2M_EVENTS (GUID, REFERENCE, STATE, EVENT_TYPE, EVENT_DATE, M2M_REJECT_CODE, COMMENT) VALUES ('` + g + `', NULL, 'TODO', 'BJ_RECEIVED', LOCALTIMESTAMP, NULL, 'тест')`,
`INSERT INTO FTV_M2M_HANDBOOK (INN, PARTICIPANT_CODE, KIND, NAME_SHORT, HANDBOOK_AT, UPDATED_AT, ACTIVE) VALUES ('7707025725', 'MC0005600000', 'DEPO', 'ЦентроКредит', LOCALTIMESTAMP, LOCALTIMESTAMP, 1)`,
} {
if _, err := db.ExecContext(ctx, q); err != nil {
t.Fatalf("%s: %v", depoStmtHead(q), err)
}
}
var state string
var tn time.Time
if err := db.QueryRowContext(ctx, `SELECT STATE, TimeNew FROM FTV_M2M WHERE GUID = $1`, g).Scan(&state, &tn); err != nil {
t.Fatal(err)
}
t.Logf("значения по умолчанию в FTV_M2M: STATE=%s TimeNew=%s", state, tn.Format("15:04:05"))
if state != "TODO" {
t.Fatalf("STATE по умолчанию %q, ожидали TODO", state)
}
}
func TestDepoTablesLiveMSSQL(t *testing.T) {
if _, err := os.Stat("/var/lib/bj/.bj/setup.json"); err != nil {
t.Skip("нет setup.json")
}
rc, err := NewRuntimeConfig("/var/lib/bj/.bj/setup.json")
if err != nil {
t.Skip(err)
}
f := rc.Snapshot().Fancy
if f.IsPostgres() || f.Server == "" {
t.Skip("в настройках не MSSQL")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
db, err := fancyOpen(f, f.DepoDB())
if err != nil {
t.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
if _, err := conn.ExecContext(ctx, "SET NOEXEC ON"); err != nil {
t.Fatal(err)
}
stmts := append(depoTablesDDL(false), depoTablesGrantSQL(false, f.User)...)
for _, s := range stmts {
if _, err := conn.ExecContext(ctx, s); err != nil {
t.Errorf("разбор %s: %v", depoStmtHead(s), err)
}
}
if _, err := conn.ExecContext(ctx, "SET NOEXEC OFF"); err != nil {
t.Fatal(err)
}
t.Logf("разобрано стейтментов MSSQL: %d", len(stmts))
msg, ok := depoTablesCheck(ctx, f)
t.Logf("проверка логином %s в %s: %v — %s", f.User, f.DepoDB(), ok, msg)
}