da12a9ec1c
- 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>
226 lines
9.0 KiB
Go
226 lines
9.0 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
|
|
)
|
|
|
|
// SeedStore — in-memory FansyStore с предзаполненными тестовыми данными
|
|
// (5 клиентов с депо-счетами и портфелями), соответствующими
|
|
// docs/fansy-contract/v1/examples/seed-data.sql. Используется в
|
|
// dev-стенде без реальной БД.
|
|
type SeedStore struct {
|
|
mu sync.Mutex // защищает динамическую регистрацию клиентов ЛК
|
|
clients map[string]*m2mcore.Client
|
|
accounts map[string][]m2mcore.DepoAccount // by client_id
|
|
}
|
|
|
|
// NewSeedStore собирает SeedStore с фиксированными тестовыми клиентами.
|
|
func NewSeedStore() *SeedStore {
|
|
s := &SeedStore{
|
|
clients: make(map[string]*m2mcore.Client),
|
|
accounts: make(map[string][]m2mcore.DepoAccount),
|
|
}
|
|
s.addClient("11111111-1111-1111-1111-111111111111",
|
|
"Иванов", "Иван", "Иванович",
|
|
m2m.DocCode21, "4512", "654321")
|
|
s.addClient("22222222-2222-2222-2222-222222222222",
|
|
"Петров", "Пётр", "Петрович",
|
|
m2m.DocCode21, "4513", "654322")
|
|
s.addClient("33333333-3333-3333-3333-333333333333",
|
|
"Сидоров", "Сидор", "Сидорович",
|
|
m2m.DocCode21, "4514", "654323")
|
|
s.addClient("44444444-4444-4444-4444-444444444444",
|
|
"Кузнецов", "Сергей", "Михайлович",
|
|
m2m.DocCode03, "111", "222333")
|
|
s.addClient("55555555-5555-5555-5555-555555555555",
|
|
"Соколова", "Анна", "Викторовна",
|
|
m2m.DocCode21, "4516", "654325")
|
|
// Тест-клиенты плана M2M (Приложение 2): К1 Мостовиков (с 16.09.2026, прогон с ГроттБьерн;
|
|
// до этого Бубликов 3453 289090), К2 Баранкин.
|
|
// ФИО и документ — как в тест-плане (m2mTestClients), чтобы EnrichRequest
|
|
// собирал СОГЛАСОВАННУЮ личность (раньше подставлялся Петров из 2222).
|
|
s.addClient(seedClientK1,
|
|
"Мостовиков", "Арсений", "Тимурович",
|
|
m2m.DocCode21, "4519", "736284")
|
|
s.addClient(seedClientK2,
|
|
"Баранкин", "Сергей", "Сергеевич",
|
|
m2m.DocCode21, "3763", "290089")
|
|
|
|
s.addAccount("11111111-1111-1111-1111-111111111111",
|
|
"DP789456", "31MC0021900000F01", "P001", "7702070139")
|
|
s.addAccount("11111111-1111-1111-1111-111111111111",
|
|
"AA789451", "33MC0021900000F02", "F002", "7802031669")
|
|
// Тест-инвестор робота: место расчётов = наш реальный тестовый счёт в НРД
|
|
// (депкод MC0413600000, счёт HL171004001C, раздел 36MC0413600000F00,
|
|
// расчётный депозитарий НРД ИНН 7702165310). Иначе НРД отвечает M2M19
|
|
// «недопустимое место расчетов».
|
|
s.addAccount("22222222-2222-2222-2222-222222222222",
|
|
"MC0413600000", "HL171004001C", "36MC0413600000F00", "7702165310")
|
|
s.addAccount("33333333-3333-3333-3333-333333333333",
|
|
"DP300400", "31MC0030000000B01", "B001", "0702345678")
|
|
s.addAccount("55555555-5555-5555-5555-555555555555",
|
|
"DP500600", "31MC0050000000C01", "C001", "0710987654")
|
|
// К1/К2 списывают с нашего реального тестового счёта в НРД (мы — Списывающий),
|
|
// то же место расчётов, что у тест-инвестора робота (иначе НРД: M2M19).
|
|
s.addAccount(seedClientK1,
|
|
"MC0413600000", "HL171004001C", "36MC0413600000F00", "7702165310")
|
|
s.addAccount(seedClientK2,
|
|
"MC0413600000", "HL171004001C", "36MC0413600000F00", "7702165310")
|
|
|
|
return s
|
|
}
|
|
|
|
// Идентификаторы сид-клиентов тест-плана M2M (реальный депозитарий, не робот).
|
|
const (
|
|
seedClientK1 = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" // Мостовиков Арсений Тимурович (К1)
|
|
seedClientK2 = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" // Баранкин Сергей Сергеевич (К2)
|
|
)
|
|
|
|
// Clients возвращает копию слайса клиентов (для UI выбора).
|
|
func (s *SeedStore) Clients() []*m2mcore.Client {
|
|
out := make([]*m2mcore.Client, 0, len(s.clients))
|
|
for _, c := range s.clients {
|
|
out = append(out, c)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *SeedStore) addClient(id, last, first, middle string, doc m2m.IdentityDocumentCode, series, number string) {
|
|
s.clients[id] = &m2mcore.Client{
|
|
ID: id,
|
|
LastName: last,
|
|
FirstName: first,
|
|
MiddleName: middle,
|
|
Document: m2mcore.ClientDocument{
|
|
DocumentType: doc,
|
|
Series: series,
|
|
Number: number,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *SeedStore) addAccount(clientID, dep, acc, sec, depINN string) {
|
|
s.accounts[clientID] = append(s.accounts[clientID], m2mcore.DepoAccount{
|
|
ClientID: clientID,
|
|
DeponentCode: dep,
|
|
AccountID: m2m.AccountID(acc),
|
|
SectionID: sec,
|
|
DepositoryINN: m2m.OrganizationINN(depINN),
|
|
})
|
|
}
|
|
|
|
// EnsureLKClient регистрирует (или обновляет) клиента Личного кабинета в
|
|
// справочнике: анкета берётся из учётной записи ЛК, депо-счета — из его
|
|
// портфеля (bj_client_positions). Нужно, чтобы EnrichRequest мог собрать
|
|
// M2MTransferRequest по заявке, поданной клиентом из кабинета.
|
|
func (s *SeedStore) EnsureLKClient(u LKUser, positions []ClientPosition, settle m2mcore.DepoAccount) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
last, first, middle := u.LastName, u.FirstName, u.MiddleName
|
|
if last == "" && u.FullName != "" {
|
|
parts := strings.Fields(u.FullName)
|
|
if len(parts) > 0 {
|
|
last = parts[0]
|
|
}
|
|
if len(parts) > 1 {
|
|
first = parts[1]
|
|
}
|
|
if len(parts) > 2 {
|
|
middle = parts[2]
|
|
}
|
|
}
|
|
docType := u.DocType
|
|
if docType == "" {
|
|
docType = "21" // паспорт РФ
|
|
}
|
|
s.clients[u.Contract] = &m2mcore.Client{
|
|
ID: u.Contract,
|
|
LastName: last,
|
|
FirstName: first,
|
|
MiddleName: middle,
|
|
Document: m2mcore.ClientDocument{
|
|
DocumentType: m2m.IdentityDocumentCode(docType),
|
|
Series: u.DocSeries,
|
|
Number: u.DocNumber,
|
|
},
|
|
}
|
|
// Депо-счета клиента — уникальные комбинации НРД-реквизитов места расчётов.
|
|
// У позиций из таблицы обмена реквизиты свои; у депозитарных позиций их нет
|
|
// (только внутренний счёт ВТНКЦ…) — тогда берём фиксированные реквизиты нашего
|
|
// депозитария в НРД (settle: deponent/account/section/ИНН из настроек).
|
|
seen := map[string]bool{}
|
|
var accs []m2mcore.DepoAccount
|
|
for _, p := range positions {
|
|
a := m2mcore.DepoAccount{
|
|
ClientID: u.Contract,
|
|
DeponentCode: p.DeponentCode,
|
|
AccountID: m2m.AccountID(p.AccountID),
|
|
SectionID: p.SectionID,
|
|
DepositoryINN: m2m.OrganizationINN(p.SettlementIN),
|
|
}
|
|
if a.DeponentCode == "" {
|
|
a.DeponentCode = settle.DeponentCode
|
|
}
|
|
if a.AccountID == "" {
|
|
a.AccountID = settle.AccountID
|
|
}
|
|
if a.SectionID == "" {
|
|
a.SectionID = settle.SectionID
|
|
}
|
|
if a.DepositoryINN == "" {
|
|
a.DepositoryINN = settle.DepositoryINN
|
|
}
|
|
key := a.DeponentCode + "|" + string(a.AccountID) + "|" + a.SectionID
|
|
if a.AccountID == "" || seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
accs = append(accs, a)
|
|
}
|
|
if len(accs) == 0 && settle.AccountID != "" {
|
|
// вообще нет позиций с реквизитами — один счёт по настройкам
|
|
settle.ClientID = u.Contract
|
|
accs = append(accs, settle)
|
|
}
|
|
if len(accs) > 0 {
|
|
s.accounts[u.Contract] = accs
|
|
}
|
|
}
|
|
|
|
// GetClientByID — реализация FansyStore.
|
|
func (s *SeedStore) GetClientByID(_ context.Context, id string) (*m2mcore.Client, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
c, ok := s.clients[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("seedstore: клиент %s не найден", id)
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// GetDepoAccounts — реализация FansyStore.
|
|
func (s *SeedStore) GetDepoAccounts(_ context.Context, clientID string, _ m2m.OrganizationINN) ([]m2mcore.DepoAccount, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
accs := s.accounts[clientID]
|
|
if len(accs) == 0 {
|
|
return nil, fmt.Errorf("seedstore: нет счетов у клиента %s", clientID)
|
|
}
|
|
return accs, nil
|
|
}
|
|
|
|
// GetBalances — реализация FansyStore. На M2 возвращает пустой список,
|
|
// потому что баланс проверяется при подаче заявки в самой UI (через демо-кнопку).
|
|
func (s *SeedStore) GetBalances(_ context.Context, _ string, _ []m2m.SecurityCode) ([]m2mcore.SecurityBalance, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Verify тип SeedStore удовлетворяет m2mcore.FansyStore.
|
|
var _ m2mcore.FansyStore = (*SeedStore)(nil)
|