Files
Bridge-and-Join-s/internal/lkgateway/realtest.go
T

248 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
// Тест «боевой ЛК → тестовый контур НРД» (29.09.2026). Заявка реального клиента из ЛК ЕСИА Финанс
// проходит у нас всё по-настоящему: проверка шлюза и поручение в DEPO, блокировка и списание в QUIK
// МНП (боевая запись — только для таких заявок, остальные остаются в имитации), статусы в ЛК. В НРД
// (TEST3) запрос уходит с подменой блока инвестора на тест-клиента, зарегистрированного в тестовом
// контуре (Бубликов): GUID, референсы, бумаги и счета — настоящие, поэтому все ответы НРД по GUID
// применяются к заявке клиента. «Исполнена» в ЛК — только по отчёту НРД об исполнении (MS010).
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
)
// RealNSDTestSettings — кто идёт в НРД с подменой и под чьими данными.
type RealNSDTestSettings struct {
Enabled bool `json:"enabled"`
// Docs — паспорта клиентов (серия номер), чьи заявки идут в НРД с подменой, через «;».
Docs string `json:"docs,omitempty"`
// As — тест-инвестор, зарегистрированный в тестовом контуре НРД. Пусто — Бубликов (К1).
AsLast string `json:"as_last,omitempty"`
AsFirst string `json:"as_first,omitempty"`
AsMiddle string `json:"as_middle,omitempty"`
AsSeries string `json:"as_series,omitempty"`
AsNumber string `json:"as_number,omitempty"`
}
func (s RealNSDTestSettings) as() (last, first, middle, ser, num string) {
if s.AsLast == "" || s.AsNumber == "" {
k := m2mTestClients["К1"]
return k.Last, k.First, k.Middle, k.DocSeries, k.DocNumber
}
return s.AsLast, s.AsFirst, s.AsMiddle, s.AsSeries, s.AsNumber
}
func normDoc(ser, num string) string {
return strings.ReplaceAll(strings.TrimSpace(ser)+strings.TrimSpace(num), " ", "")
}
// realTestMatch — заявка этого клиента идёт в НРД с подменой.
func realTestMatch(rc *RuntimeConfig, ser, num string) bool {
if rc == nil {
return false
}
t := rc.Snapshot().LK.RealNSDTest
if !t.Enabled || normDoc(ser, num) == "" {
return false
}
for _, d := range strings.Split(t.Docs, ";") {
f := strings.Fields(d)
if len(f) >= 1 && normDoc(strings.Join(f[:len(f)-1], ""), f[len(f)-1]) == normDoc(ser, num) {
return true
}
}
return false
}
// RealTestRec — заявка, ушедшая в НРД с подменой.
type RealTestRec struct {
GUID string `json:"guid"`
ClaimID int `json:"claim_id,omitempty"` // заявка ЛК
Client string `json:"client"` // ФИО и паспорт клиента
As string `json:"as"` // под чьими данными ушло в НРД
At time.Time `json:"at"`
LKDone bool `json:"lk_done,omitempty"` // итог передан в ЛК
}
var realTestFile = "/var/lib/bj/.bj/real-nsd-test.json"
var realTests = struct {
sync.Mutex
loaded bool
m map[string]*RealTestRec
}{}
func realTestLoad() {
if realTests.loaded {
return
}
realTests.loaded, realTests.m = true, map[string]*RealTestRec{}
if raw, err := os.ReadFile(realTestFile); err == nil {
_ = json.Unmarshal(raw, &realTests.m)
}
}
func realTestSave() {
if raw, err := json.MarshalIndent(realTests.m, "", " "); err == nil {
_ = os.WriteFile(realTestFile, raw, 0o600)
}
}
func realTestGet(guid string) *RealTestRec {
if guid == "" {
return nil
}
realTests.Lock()
defer realTests.Unlock()
realTestLoad()
if r, ok := realTests.m[strings.ToLower(guid)]; ok {
cp := *r
return &cp
}
return nil
}
func realTestPut(r RealTestRec) {
realTests.Lock()
defer realTests.Unlock()
realTestLoad()
r.GUID = strings.ToLower(r.GUID)
realTests.m[r.GUID] = &r
realTestSave()
}
// realTestAny — есть тестовые заявки, по которым ещё ждём итог (опросчик QUIK должен работать).
func realTestAny() bool {
realTests.Lock()
defer realTests.Unlock()
realTestLoad()
for _, r := range realTests.m {
if !r.LKDone {
return true
}
}
return false
}
func realTestMarkLKDone(guid string) bool {
realTests.Lock()
defer realTests.Unlock()
realTestLoad()
r, ok := realTests.m[strings.ToLower(guid)]
if !ok || r.LKDone {
return false
}
r.LKDone = true
realTestSave()
return true
}
// substituteSender — отправитель в НРД с подменой блока инвестора; сделка у нас хранит исходный запрос.
type substituteSender struct {
real m2mcore.NSDSender
inv m2m.InvestorInformation
}
func (s substituteSender) Send(ctx context.Context, req *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) {
cp := *req
cp.Data.InvestorInformation = s.inv
return s.real.Send(ctx, &cp)
}
func (s substituteSender) SendDecision(ctx context.Context, d *m2m.M2MTransferDecision) error {
return s.real.SendDecision(ctx, d)
}
// realTestInvestor — блок инвестора тест-клиента в формате запроса НРД; тип документа — как у клиента.
func realTestInvestor(rc *RuntimeConfig, docType m2m.IdentityDocumentCode) (m2m.InvestorInformation, string) {
last, first, middle, ser, num := rc.Snapshot().LK.RealNSDTest.as()
inv := m2m.InvestorInformation{LastName: last, FirstName: first, MiddleName: middle}
inv.IdentityDocument.DocumentType = docType
if ser != "" {
sv := m2m.IdentityDocSerial(ser)
inv.IdentityDocument.DocumentSeries = &sv
}
inv.IdentityDocument.DocumentNumber = m2m.IdentityDocSerial(num)
return inv, strings.TrimSpace(fmt.Sprintf("%s %s %s, %s %s", last, first, middle, ser, num))
}
// realTestSenderFor — отправитель для заявки: с подменой, если заявка из теста; иначе def.
func realTestSenderFor(rc *RuntimeConfig, guid string, req *m2m.M2MTransferRequest, def m2mcore.NSDSender) m2mcore.NSDSender {
if realTestGet(guid) == nil || req == nil || rc == nil {
return def
}
inv, _ := realTestInvestor(rc, req.Data.InvestorInformation.IdentityDocument.DocumentType)
return substituteSender{real: def, inv: inv}
}
// quikSettingsFor — настройки МНП для перевода: у тестовых заявок «боевой ЛК → TEST3» запись в МНП
// настоящая (по решению 29.09.2026), у остальных — как в настройках.
func quikSettingsFor(rc *RuntimeConfig, guid string) QuikMNPSettings {
s := rc.Snapshot().Quik
if realTestGet(guid) != nil {
s.Emulate, s.WritesEnabled = false, true
}
return s
}
// realTestOnExecuted — НРД исполнил (или отверг) наше поручение 16: итог — в ЛК.
func realTestOnExecuted(ctx context.Context, rc *RuntimeConfig, guid string, rejected bool, partial string) {
if realTestGet(guid) == nil || !realTestMarkLKDone(guid) {
return
}
status, reason := "done", "Перевод исполнен: бумаги списаны"
switch {
case rejected && partial == "":
status, reason = "rejected", "НРД не исполнил поручение на списание"
case partial != "":
reason = "Перевод исполнен частично: " + partial
}
globalLKFlows.stepIfExists(guid, pick3(status == "done", "response", "reject"), "Итог НРД (MS010) → статус в ЛК", reason)
OnLKOutcome(ctx, rc, guid, status, reason)
log.Printf("real-test: %s — итог в ЛК: %s (%s)", shortGUID(guid), status, reason)
}
// registerRealNSDTest — POST /admin/setup/lk-real-nsd: включить/выключить тест и список клиентов
// (в «Настройка → ЛК ЕСИА Финанс», доступно без режима разработки — право «настройка»).
func registerRealNSDTest(mux *http.ServeMux, rc *RuntimeConfig) {
mux.HandleFunc("/admin/setup/lk-real-nsd", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
lk := rc.Snapshot().LK
lk.RealNSDTest.Enabled = r.FormValue("enabled") != ""
lk.RealNSDTest.Docs = strings.TrimSpace(r.FormValue("docs"))
if err := rc.UpdateLK(lk); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("real-test: %v, клиенты: %s", lk.RealNSDTest.Enabled, lk.RealNSDTest.Docs)
back := "/admin/setup"
if ref := r.Referer(); strings.Contains(ref, "/admin/m2m-testing") {
back = "/admin/m2m-testing"
}
http.Redirect(w, r, back+"#lk-real-nsd", http.StatusSeeOther)
})
}
// realNSDAsText — тест-инвестор для подписи в настройках.
func realNSDAsText() string {
if globalRC == nil {
return ""
}
l, f, m, sr, n := globalRC.Snapshot().LK.RealNSDTest.as()
return strings.TrimSpace(l + " " + f + " " + m + ", " + sr + " " + n)
}