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) + } +}