From 7a84dd6d5b6793c127c7c0e5e79af720b0b7ac2a Mon Sep 17 00:00:00 2001 From: zuevav Date: Tue, 29 Sep 2026 13:29:25 +0300 Subject: [PATCH] =?UTF-8?q?QUIK=20=D0=9C=D0=9D=D0=9F:=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=20M2M=20=E2=80=94=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20(3=C3=973?= =?UTF-8?q?0=20=D1=81),=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D1=83=D0=BB=D1=8C=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=85?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B8=D1=82=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA;=20=D0=B8=D0=BC=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=9D=D0=A0=D0=94;=20=D0=BE=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=20=D0=9B=D0=9A=20=E2=80=94=20=D0=B4=D0=BE=204=20=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD=20=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D1=85=D0=BE?= =?UTF-8?q?=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- internal/lkgateway/lkconnector.go | 3 ++- internal/lkgateway/quikcycle.go | 33 ++++++++++++++++++++--- internal/lkgateway/quikrecip_live_test.go | 32 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 internal/lkgateway/quikrecip_live_test.go diff --git a/internal/lkgateway/lkconnector.go b/internal/lkgateway/lkconnector.go index 6616d74..c5ae5f2 100644 --- a/internal/lkgateway/lkconnector.go +++ b/internal/lkgateway/lkconnector.go @@ -279,7 +279,8 @@ func (s *Server) pollLKClaims(ctx context.Context) { if !moduleAllowed(s.rc, "esia-finance") { continue // ЕСИА Финанс — отдельный модуль лицензии } - cctx, cancel := context.WithTimeout(ctx, 50*time.Second) + // до 4 мин: блокировка в QUIK МНП может ждать, пока модуль подхватит нового получателя + cctx, cancel := context.WithTimeout(ctx, 4*time.Minute) res, err := PullLKClaims(cctx, s.rc, s.svc) cancel() if err != nil { diff --git a/internal/lkgateway/quikcycle.go b/internal/lkgateway/quikcycle.go index f194d1f..7a3b587 100644 --- a/internal/lkgateway/quikcycle.go +++ b/internal/lkgateway/quikcycle.go @@ -491,6 +491,12 @@ 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} } +// Повтор регистрации, пока модуль МНП не подхватил нового получателя из M2MDepoRecipients. +var ( + quikRecipientRetries = 3 + quikRecipientWait = 30 * time.Second +) + // QuikOutRegister — исходящий перевод: зарегистрировать «Вывод ЦБ (M2M)» (QUIK блокирует бумаги). func QuikOutRegister(ctx context.Context, rc *RuntimeConfig, guid, clientID, contract, isin string, qty float64, recipientINN, recipientName string, passExpenses bool) (*QuikLink, error) { s := quikSettingsFor(rc, guid) @@ -500,14 +506,33 @@ func QuikOutRegister(ctx context.Context, rc *RuntimeConfig, guid, clientID, con if s.Emulate { return quikEmulated(rc, guid, isin, "register", "registered", "вывод зарегистрирован, бумаги заблокированы"), nil } + if strings.TrimSpace(recipientName) == "" { + recipientName = m2mPartyName(recipientINN) // наименование из справочника НРД + } if err := quikEnsureRecipient(ctx, s, recipientINN, recipientName); err != nil { return quikFail(rc, guid, isin, "register", err), err } - aid, err := quikExecNewInstr(ctx, s, true, shortGUID(guid), clientID, isin, qty, recipientINN, passExpenses, "M2M bj: перевод "+shortGUID(guid)) - if err != nil { - return quikFail(rc, guid, isin, "register", err), err + var aid int64 + var r QuikActionResult + // Нового получателя (только что добавлен в M2MDepoRecipients) модуль МНП подхватывает не сразу — + // «Неизвестный депозитарий получателя»: ждём и повторяем, а не отклоняем заявку клиента. + for attempt := 1; ; attempt++ { + var err error + aid, err = quikExecNewInstr(ctx, s, true, shortGUID(guid), clientID, isin, qty, recipientINN, passExpenses, "M2M bj: перевод "+shortGUID(guid)) + if err != nil { + return quikFail(rc, guid, isin, "register", err), err + } + r = quikWaitAction(ctx, s, aid, quikActionWait) + if attempt > quikRecipientRetries || !strings.Contains(strings.ToLower(r.ReplyText), "неизвестный депозитарий получателя") { + break + } + log.Printf("quik: %s: МНП ещё не знает получателя ИНН %s — повтор через %s (попытка %d из %d)", shortGUID(guid), recipientINN, quikRecipientWait, attempt, quikRecipientRetries) + select { + case <-ctx.Done(): + return quikFail(rc, guid, isin, "register", ctx.Err()), ctx.Err() + case <-time.After(quikRecipientWait): + } } - 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("register", aid, r, false)) }) diff --git a/internal/lkgateway/quikrecip_live_test.go b/internal/lkgateway/quikrecip_live_test.go new file mode 100644 index 0000000..1c6a965 --- /dev/null +++ b/internal/lkgateway/quikrecip_live_test.go @@ -0,0 +1,32 @@ +//go:build live + +package lkgateway + +import ( + "context" + "fmt" + "testing" +) + +// Только чтение: справочник депозитариев-получателей МНП. +func TestQuikRecipientsLive(t *testing.T) { + rc, err := NewRuntimeConfig("/var/lib/bj/.bj/setup.json") + if err != nil { + t.Fatal(err) + } + conn, err := quikOpen(rc.Snapshot().Quik) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + rows, err := conn.QueryContext(context.Background(), "SELECT Id, INN, Name FROM M2MDepoRecipients ORDER BY Id") + if err != nil { + t.Fatal(err) + } + for rows.Next() { + var id int + var inn, name string + _ = rows.Scan(&id, &inn, &name) + fmt.Println(id, inn, name) + } +}