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

176 lines
7.5 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
// Проверка модуля неторговых поручений QUIK (МНП) — только чтение (05.10.2026). QUIK у депозитариев
// стандартный, поэтому проверяется то, без чего цикл M2M в МНП не работает: объекты API, инструменты
// классов INSTROUTM2M/INSTRINM2M, справочник депозитариев-получателей, включённый автоматический цикл и
// ошибки последних операций. Вызывается кнопкой на шаге «QUIK МНП» и в итоговой проверке готовности.
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
)
// MNPCheck — одна строка проверки МНП.
type MNPCheck struct {
Title string `json:"title"`
State string `json:"state"` // ok | warn | err
Detail string `json:"detail"`
}
func quikMNPCheck(ctx context.Context, s QuikMNPSettings) []MNPCheck {
var out []MNPCheck
add := func(title, state, detail string) { out = append(out, MNPCheck{title, state, detail}) }
if strings.TrimSpace(s.Server) == "" {
add("Подключение к базе МНП", "err", "сервер МНП не указан")
return out
}
res, err := quikProbe(ctx, s)
if err != nil {
add("Подключение к базе МНП", "err", quikErrorDetail(err))
return out
}
add("Подключение к базе МНП", "ok", fmt.Sprintf("база %s, логин %s", res.DBName, res.Login))
var miss []string
for _, k := range []string{"table:Export", "table:M2MDepoRecipients", "table:Contracts", "proc:sp_NewInstrSec", "proc:sp_SignInstrSec", "proc:cancel_instruction"} {
if !res.Objects[k] {
miss = append(miss, strings.SplitN(k, ":", 2)[1])
}
}
if len(miss) > 0 {
add("Объекты API МНП для M2M", "err", "под этим логином не видно: "+strings.Join(miss, ", ")+". Нужна версия МНП с поддержкой M2M и права логина (роль InstrEu) — к администратору QUIK / ARQA")
} else {
add("Объекты API МНП для M2M", "ok", "таблицы и процедуры поручений M2M на месте")
}
conn, err := quikOpen(s)
if err != nil {
add("Инструменты M2M", "err", quikErrorDetail(err))
return out
}
defer conn.Close()
cctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
found := map[string]string{}
if rows, err := conn.QueryContext(cctx, fsql.Qk01); err == nil {
for rows.Next() {
var cls, sec string
if rows.Scan(&cls, &sec) == nil && found[cls] == "" {
found[cls] = sec
}
}
rows.Close()
} else {
add("Инструменты M2M", "err", "справочник инструментов: "+quikErrorDetail(err))
}
inst := func(cls, manual, what string) {
switch {
case strings.TrimSpace(manual) != "":
add("Инструмент «"+what+"»", "ok", "задан в настройках: "+manual)
case found[cls] != "":
add("Инструмент «"+what+"»", "ok", "класс "+cls+", инструмент "+found[cls])
default:
add("Инструмент «"+what+"»", "err", "в МНП нет включённого инструмента класса "+cls+" — администратору QUIK: включить класс и типы поручений M2M в QAdmin")
}
}
inst("INSTROUTM2M", s.SecOut, "Вывод ЦБ (M2M)")
inst("INSTRINM2M", s.SecIn, "Ввод ЦБ (M2M)")
var nrec int
if err := conn.QueryRowContext(cctx, "SELECT COUNT(*) FROM M2MDepoRecipients").Scan(&nrec); err != nil {
add("Справочник депозитариев-получателей", "err", quikErrorDetail(err))
} else {
quikRecipientsState.Lock()
total, at, rerr := quikRecipientsState.Total, quikRecipientsState.At, quikRecipientsState.Err
quikRecipientsState.Unlock()
switch {
case nrec == 0:
add("Справочник депозитариев-получателей", "err", "пуст — bj заполнит его по справочнику НРД (раз в 30 мин до 20:00); после первого заполнения модуль МНП нужно перезапустить")
case rerr != "":
add("Справочник депозитариев-получателей", "warn", fmt.Sprintf("в МНП %d записей; последнее обновление с ошибкой: %s", nrec, rerr))
case total > 0 && nrec < total:
add("Справочник депозитариев-получателей", "warn", fmt.Sprintf("в МНП %d из %d депозитариев НРД — bj дополнит при следующей сверке", nrec, total))
default:
d := fmt.Sprintf("%d депозитариев", nrec)
if !at.IsZero() {
d += ", сверен " + at.Format("02.01 15:04")
}
add("Справочник депозитариев-получателей", "ok", d+". Модуль МНП читает справочник при запуске — после первого заполнения его перезапускают")
}
}
switch {
case !s.WritesEnabled:
add("Автоматический цикл M2M", "warn", "запись в МНП выключена — bj не ставит и не снимает блокировки (Настройка → QUIK МНП)")
case s.Emulate:
add("Автоматический цикл M2M", "warn", "включена имитация — шаги проходят без записи в МНП")
case !s.AutoOut && !s.AutoIn:
add("Автоматический цикл M2M", "warn", "запись разрешена, но исходящие и входящие поручения в МНП не регистрируются автоматически")
default:
var on []string
if s.AutoOut {
on = append(on, "исходящие")
}
if s.AutoIn {
on = append(on, "входящие")
}
if s.PollTerminal {
on = append(on, "поручения из терминала")
}
add("Автоматический цикл M2M", "ok", "включён: "+strings.Join(on, ", ")+"; торговый счёт "+quikAccount(s))
}
since := time.Now().AddDate(0, 0, -30)
var n, nerr int
var lastErr *QuikLink
for _, l := range globalQuikLinks.List(500) {
if l.UpdatedAt.Before(since) {
continue
}
n++
if l.State == "error" {
nerr++
if lastErr == nil || l.UpdatedAt.After(lastErr.UpdatedAt) {
lastErr = l
}
}
}
switch {
case n == 0:
add("Операции в МНП за 30 дней", "ok", "ещё не было")
case nerr > 0:
add("Операции в МНП за 30 дней", "warn", fmt.Sprintf("%d, из них с ошибкой %d; последняя (%s): %s", n, nerr, lastErr.UpdatedAt.Format("02.01 15:04"), lastErr.Err))
default:
add("Операции в МНП за 30 дней", "ok", fmt.Sprintf("%d, без ошибок", n))
}
return out
}
func mnpWorst(cs []MNPCheck) string {
w := "ok"
for _, c := range cs {
if c.State == "err" {
return "err"
}
if c.State == "warn" {
w = "warn"
}
}
return w
}
func registerQuikReady(mux *http.ServeMux, rc *RuntimeConfig) {
mux.HandleFunc("/admin/setup/quik/mnp-check", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
cs := quikMNPCheck(ctx, rc.Snapshot().Quik)
jsonOut(w, map[string]any{"checks": cs, "state": mnpWorst(cs), "at": time.Now().Format("02.01.2006 15:04:05")})
})
}