feat(admin): эмулятор МНП — режим реальной записи в МНП на подставного клиента
Третий режим МНП (между «внутренняя имитация» и «боевая запись»): в bj цикл идёт по обычному пользователю, а поручение в QUIK МНП уходит РЕАЛЬНО, но с кодом другого, реально существующего в QUIK клиента (обобщение ручного теста realtest «боевой ЛК → TEST3»). - QuikMNPSettings: RealWrite + RealClientCode (валидация формата кода). - Подмена кода — единая точка quikClientCodeFromPort (после ClientCodeMap); портфель не трогаем. quikActive: RealWrite только при devMode и непустом коде; Emulate (внутренняя имитация) в приоритете. - Шаги боевого пути помечаются «[на подставного клиента <код>]» — видно в «Поручениях QUIK». - Карточка МНП на /admin/emulators — три взаимоисключающих режима (выключено / имитация / реальная запись), поле кода, крупное предупреждение; отдельный красный баннер «Реальные поручения в МНП на клиента <код> (режим разработки)» — не смешиваем с баннером эмуляции НРД. - disableEmulatorsIfDevOff сбрасывает и RealWrite (код остаётся, инертен), новость явно фиксирует прекращение реальных поручений.
This commit is contained in:
@@ -412,22 +412,32 @@ func quikExecDeny(ctx context.Context, s QuikMNPSettings, queryNo float64, reaso
|
||||
var quikPortRe = regexp.MustCompile(`^\s*(\S+)`)
|
||||
|
||||
// quikClientCodeFromPort: «БР-859 Иванов…» → «BR859»; исключения — из настроек.
|
||||
// В режиме «реальная запись на подставного клиента» (RealWrite, страница «Эмуляторы»)
|
||||
// код подменяется в самом конце — поручения в МНП уходят на RealClientCode.
|
||||
func quikClientCodeFromPort(s QuikMNPSettings, portNum string) string {
|
||||
m := quikPortRe.FindStringSubmatch(portNum)
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
tok := m[1]
|
||||
code := ""
|
||||
if s.ClientCodeMap != nil {
|
||||
if v, ok := s.ClientCodeMap[tok]; ok && strings.TrimSpace(v) != "" {
|
||||
return strings.TrimSpace(v)
|
||||
if v, ok := s.ClientCodeMap[tok]; ok {
|
||||
code = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
up := strings.ToUpper(tok)
|
||||
if strings.HasPrefix(up, "БР-") {
|
||||
return "BR" + strings.TrimPrefix(up, "БР-")
|
||||
if code == "" {
|
||||
up := strings.ToUpper(tok)
|
||||
if strings.HasPrefix(up, "БР-") {
|
||||
code = "BR" + strings.TrimPrefix(up, "БР-")
|
||||
} else {
|
||||
code = tok
|
||||
}
|
||||
}
|
||||
return tok
|
||||
if s.RealWrite && strings.TrimSpace(s.RealClientCode) != "" {
|
||||
return strings.TrimSpace(s.RealClientCode)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// quikResolveClient находит в DEPO клиента по паспорту и возвращает код QUIK и номер портфеля.
|
||||
@@ -463,12 +473,16 @@ func quikResolveClient(ctx context.Context, rc *RuntimeConfig, ser, num string)
|
||||
|
||||
// ---- высокоуровневые операции цикла ----
|
||||
|
||||
// quikActive — цикл M2M в МНП работает: записи разрешены или включена имитация. Имитация — только в
|
||||
// режиме разработки; если он закончился при включённой имитации, цикл стоит (в МНП не пишем).
|
||||
// quikActive — цикл M2M в МНП работает: записи разрешены, включена имитация или режим реальной
|
||||
// записи на подставного клиента. Имитация и real-запись — только в режиме разработки; если он
|
||||
// закончился при включённом режиме, цикл стоит (в МНП не пишем). Имитация имеет приоритет.
|
||||
func quikActive(s QuikMNPSettings) bool {
|
||||
if s.Emulate {
|
||||
return devMode(globalRC)
|
||||
}
|
||||
if s.RealWrite {
|
||||
return devMode(globalRC) && strings.TrimSpace(s.RealClientCode) != ""
|
||||
}
|
||||
return s.WritesEnabled
|
||||
}
|
||||
|
||||
@@ -512,6 +526,16 @@ func quikStep(action string, aid int64, r QuikActionResult, ok bool) QuikStep {
|
||||
return QuikStep{At: time.Now(), Action: action, ActionID: aid, Status: r.QueryStatus, Reply: r.ReplyText, OK: ok}
|
||||
}
|
||||
|
||||
// quikStepReal — шаг боевого пути с пометкой режима «реальная запись на подставного
|
||||
// клиента»: в UI «Поручения QUIK» видно, что поручение ушло не на своего клиента.
|
||||
func quikStepReal(s QuikMNPSettings, action string, aid int64, r QuikActionResult, ok bool) QuikStep {
|
||||
st := quikStep(action, aid, r, ok)
|
||||
if s.RealWrite && strings.TrimSpace(s.RealClientCode) != "" {
|
||||
st.Reply = strings.TrimSpace(st.Reply + " [на подставного клиента " + strings.TrimSpace(s.RealClientCode) + "]")
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
// Повтор регистрации, пока модуль МНП не подхватил нового получателя из M2MDepoRecipients.
|
||||
var (
|
||||
quikRecipientRetries = 3
|
||||
@@ -556,12 +580,12 @@ func QuikOutRegister(ctx context.Context, rc *RuntimeConfig, guid, clientID, con
|
||||
}
|
||||
if r.QueryStatus != 2 || r.InstrNo == "" || r.InstrNo == "0" {
|
||||
err := fmt.Errorf("МНП отверг регистрацию: %s", r.ReplyText)
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStep("register", aid, r, false)) })
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStepReal(s, "register", aid, r, false)) })
|
||||
return quikFail(rc, guid, isin, "register", err), err
|
||||
}
|
||||
l = globalQuikLinks.update(guid, isin, func(l *QuikLink) {
|
||||
l.InstrNo, l.State, l.Err = r.InstrNo, "registered", ""
|
||||
l.Steps = append(l.Steps, quikStep("register", aid, r, true))
|
||||
l.Steps = append(l.Steps, quikStepReal(s, "register", aid, r, true))
|
||||
})
|
||||
log.Printf("quik: %s вывод зарегистрирован, поручение %s (%s, %s × %v)", shortGUID(guid), r.InstrNo, clientID, isin, qty)
|
||||
go depoOnQuik(rc, guid, isin, "register", r.InstrNo)
|
||||
@@ -590,12 +614,12 @@ func QuikOutSign(ctx context.Context, rc *RuntimeConfig, guid, isin, comment str
|
||||
r := quikWaitAction(ctx, s, aid, quikActionWait)
|
||||
if r.QueryStatus != 2 {
|
||||
err := fmt.Errorf("МНП отверг подпись: %s", r.ReplyText)
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStep("sign", aid, r, false)) })
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStepReal(s, "sign", aid, r, false)) })
|
||||
return quikFail(rc, guid, isin, "sign", err), err
|
||||
}
|
||||
l = globalQuikLinks.update(guid, isin, func(l *QuikLink) {
|
||||
l.State, l.Err = "signed", ""
|
||||
l.Steps = append(l.Steps, quikStep("sign", aid, r, true))
|
||||
l.Steps = append(l.Steps, quikStepReal(s, "sign", aid, r, true))
|
||||
})
|
||||
log.Printf("quik: %s поручение %s подписано и исполнено", shortGUID(guid), l.InstrNo)
|
||||
go depoOnQuik(rc, guid, isin, "sign", l.InstrNo)
|
||||
@@ -624,12 +648,12 @@ func QuikOutDeny(ctx context.Context, rc *RuntimeConfig, guid, isin, reason stri
|
||||
r := quikWaitAction(ctx, s, aid, quikActionWait)
|
||||
if r.QueryStatus != 2 {
|
||||
err := fmt.Errorf("МНП отверг снятие: %s", r.ReplyText)
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStep("deny", aid, r, false)) })
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStepReal(s, "deny", aid, r, false)) })
|
||||
return quikFail(rc, guid, isin, "deny", err), err
|
||||
}
|
||||
l = globalQuikLinks.update(guid, isin, func(l *QuikLink) {
|
||||
l.State, l.Err = "denied", ""
|
||||
l.Steps = append(l.Steps, quikStep("deny", aid, r, true))
|
||||
l.Steps = append(l.Steps, quikStepReal(s, "deny", aid, r, true))
|
||||
})
|
||||
log.Printf("quik: %s поручение %s снято: %s", shortGUID(guid), l.InstrNo, reason)
|
||||
go depoOnQuik(rc, guid, isin, "deny", l.InstrNo)
|
||||
@@ -658,12 +682,12 @@ func QuikInDeposit(ctx context.Context, rc *RuntimeConfig, guid, clientID, contr
|
||||
r := quikWaitAction(ctx, s, aid, quikActionWait)
|
||||
if r.QueryStatus != 2 || r.InstrNo == "" || r.InstrNo == "0" {
|
||||
err := fmt.Errorf("МНП отверг зачисление: %s", r.ReplyText)
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStep("deposit", aid, r, false)) })
|
||||
globalQuikLinks.update(guid, isin, func(l *QuikLink) { l.Steps = append(l.Steps, quikStepReal(s, "deposit", aid, r, false)) })
|
||||
return quikFail(rc, guid, isin, "deposit", err), err
|
||||
}
|
||||
l := globalQuikLinks.update(guid, isin, func(l *QuikLink) {
|
||||
l.InstrNo, l.State, l.Err = r.InstrNo, "deposited", ""
|
||||
l.Steps = append(l.Steps, quikStep("deposit", aid, r, true))
|
||||
l.Steps = append(l.Steps, quikStepReal(s, "deposit", aid, r, true))
|
||||
})
|
||||
log.Printf("quik: %s ввод зарегистрирован и исполнен, поручение %s (%s, %s × %v)", shortGUID(guid), r.InstrNo, clientID, isin, qty)
|
||||
go depoOnQuik(rc, guid, isin, "deposit", r.InstrNo)
|
||||
|
||||
Reference in New Issue
Block a user