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:
zuevav
2026-10-08 00:45:06 +03:00
parent 8e4cb5de19
commit 6c42e0378d
10 changed files with 293 additions and 81 deletions
+19 -10
View File
@@ -1,11 +1,12 @@
package lkgateway
import (
"log"
"encoding/json"
"errors"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
@@ -69,15 +70,17 @@ type QuikMNPSettings struct {
M2MMissing string `json:"m2m_missing,omitempty"` // ключевые объекты M2M, не видные под логином при последней проверке
// Автоматический цикл M2M в МНП (пишущие операции к боевой БД).
WritesEnabled bool `json:"writes_enabled"` // главный предохранитель: без него ни одной записи в МНП
Emulate bool `json:"emulate"` // имитация: шаги цикла проходят без записи в МНП (нет пользователя QUIK для блокировок)
AutoOut bool `json:"auto_out"` // исходящие: регистрировать «Вывод ЦБ (M2M)» при отправке, подписывать/снимать по ответу
AutoIn bool `json:"auto_in"` // входящие: регистрировать «Ввод ЦБ (M2M)» после приёма поручения НРД
PollTerminal bool `json:"poll_terminal"` // подхватывать поручения, поданные клиентами из QUIK (Export)
Account string `json:"account"` // торговый счёт для регистрации (напр. L01+00000F00)
SecOut string `json:"sec_out"` // код МНП-инструмента вывода (пусто = определить по классу INSTROUTM2M)
SecIn string `json:"sec_in"` // код МНП-инструмента ввода (пусто = по классу INSTRINM2M)
ClientCodeMap map[string]string `json:"client_code_map"` // исключения: номер портфеля DEPO («БР-4-4») → код клиента QUIK
WritesEnabled bool `json:"writes_enabled"` // главный предохранитель: без него ни одной записи в МНП
Emulate bool `json:"emulate"` // имитация: шаги цикла проходят без записи в МНП (нет пользователя QUIK для блокировок)
RealWrite bool `json:"real_write"` // режим разработки: РЕАЛЬНАЯ запись поручений в боевую МНП, но на подставного клиента
RealClientCode string `json:"real_client_code,omitempty"` // код реального клиента QUIK, на которого уходят поручения в режиме real_write
AutoOut bool `json:"auto_out"` // исходящие: регистрировать «Вывод ЦБ (M2M)» при отправке, подписывать/снимать по ответу
AutoIn bool `json:"auto_in"` // входящие: регистрировать «Ввод ЦБ (M2M)» после приёма поручения НРД
PollTerminal bool `json:"poll_terminal"` // подхватывать поручения, поданные клиентами из QUIK (Export)
Account string `json:"account"` // торговый счёт для регистрации (напр. L01+00000F00)
SecOut string `json:"sec_out"` // код МНП-инструмента вывода (пусто = определить по классу INSTROUTM2M)
SecIn string `json:"sec_in"` // код МНП-инструмента ввода (пусто = по классу INSTRINM2M)
ClientCodeMap map[string]string `json:"client_code_map"` // исключения: номер портфеля DEPO («БР-4-4») → код клиента QUIK
}
// LicenseSettings — лицензионный ключ (подписанный токен).
@@ -516,8 +519,14 @@ func (r *RuntimeConfig) UpdateFancy(s FancyMSSQLSettings) error {
return r.save()
}
// quikRealClientCodeRe — код подставного клиента QUIK (режим real_write): латиница/цифры/дефис/подчёркивание.
var quikRealClientCodeRe = regexp.MustCompile(`^[A-Za-z0-9_-]{2,32}$`)
// UpdateQuik сохраняет настройки подключения к БД МНП QUIK.
func (r *RuntimeConfig) UpdateQuik(s QuikMNPSettings) error {
if c := strings.TrimSpace(s.RealClientCode); c != "" && !quikRealClientCodeRe.MatchString(c) {
return errors.New("код подставного клиента QUIK — 2..32 символа: латиница, цифры, дефис, подчёркивание")
}
r.mu.Lock()
r.data.Quik = s
r.data.UpdatedAt = time.Now().UTC()