Мастер, QUIK: короткое письмо в ARQA с внешним адресом, памятка администратору, понятный выбор работы с QUIK

- Письмо в ARQA: организация просит подключить модуль неторговых поручений,
  выдать логин и пароль и разрешить подключение с нашего внешнего адреса —
  о bj и внутреннем устройстве ни слова. Внешний адрес bj узнаёт у сервера
  лицензий (GET /v1/ip, раз в час); пометка — убедиться, что адрес постоянный.
- Под письмом — «Памятка администратору QUIK» (классы, логин InstrEu, справочник,
  перезапуск модуля) со ссылкой на подробную инструкцию.
- Вместо свёрнутого «Автоматический цикл M2M — выключен · настроить» и справочника
  под раскрытием: после проверки подключения — «Как bj работает с QUIK»: «Только
  сверка» или «Автоматически» (+ торговый счёт), сохраняется сразу; справочник —
  строкой состояния с напоминанием о перезапуске. До проверки — одна строка.
  Тонкая настройка — в «Настройка → QUIK», мастер её не трогает.
- Проверка МНП: ошибка сертификата (x509, «doesn't contain any IP SANs») —
  подсказка включить «Доверять сертификату сервера», «Дополнительно» раскрывается.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 16:59:21 +03:00
parent 8ab3a88c7b
commit a7cfbb2e74
8 changed files with 284 additions and 11 deletions
+61
View File
@@ -17,6 +17,7 @@ func (h *setupHandlers) registerQuik(mux *http.ServeMux) {
mux.HandleFunc("/admin/setup/quik/test", h.testQuik)
mux.HandleFunc("/admin/quik", h.quikInstructions)
mux.HandleFunc("/admin/setup/quik/cycle", h.saveQuikCycle)
mux.HandleFunc("/admin/setup/quik/mode", h.saveQuikMode)
}
// quikInstructions — GET /admin/quik. Поручения M2M из БД МНП QUIK.
@@ -122,6 +123,43 @@ func (h *setupHandlers) saveQuikCycle(w http.ResponseWriter, r *http.Request) {
setupFlash(w, r, "QUIK МНП: цикл M2M сохранён — "+mode)
}
// saveQuikMode — POST /admin/setup/quik/mode (мастер): как bj работает с QUIK — одним выбором вместо набора
// флажков. check — только сверка (в МНП bj не пишет, поручения вводят сотрудники); auto — bj ведёт поручения
// сам: исходящие (блокировка, списание или снятие) и входящие (зачисление). Тонкая настройка (инструменты,
// коды клиентов, поручения из терминала, имитация) — в «Настройке», здесь не трогается. Экран сохраняет
// выбор сразу (X-BJ-Ajax — ответ строкой), без JavaScript — обычной формой.
func (h *setupHandlers) saveQuikMode(w http.ResponseWriter, r *http.Request) {
if !requireAdvanced(w, r) {
return
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().Quik
auto := r.FormValue("mode") == "auto"
s.WritesEnabled, s.AutoOut, s.AutoIn = auto, auto, auto
if r.PostForm.Has("account") {
s.Account = strings.TrimSpace(r.FormValue("account"))
}
msg := "только сверка — в QUIK bj ничего не пишет"
if auto {
msg = "автоматически — bj ведёт поручения в QUIK сам"
if s.Account == "" {
msg += "; укажите торговый счёт"
}
}
if err := h.rc.UpdateQuik(s); err != nil {
msg = "не сохранилось: " + err.Error()
}
if r.Header.Get("X-BJ-Ajax") != "" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte(msg))
return
}
setupFlash(w, r, "QUIK: "+msg)
}
func onOff(b bool) string {
if b {
return "авто"
@@ -168,6 +206,21 @@ func (h *setupHandlers) saveQuik(w http.ResponseWriter, r *http.Request) {
setupFlash(w, r, fmt.Sprintf("QUIK МНП: настройки сохранены (%s:%d, логин %s)", s.Server, s.Port, s.User))
}
// quikCertHint — что делать, когда сервер МНП не прошёл проверку сертификата: у QUIK сертификат обычно свой
// (самоподписанный или на имя сервера, а подключаемся по IP).
const quikCertHint = "сертификат сервера МНП выписан не на этот адрес или самоподписанный (так у QUIK обычно и бывает). Включите «Доверять сертификату сервера» в «Дополнительно: база и шифрование» и проверьте ещё раз — соединение останется зашифрованным."
// quikCertError — подключение не прошло из-за проверки сертификата сервера (не из-за сети или логина).
func quikCertError(err error) bool {
m := strings.ToLower(err.Error())
return strings.Contains(m, "x509:") || strings.Contains(m, "failed to verify certificate") || strings.Contains(m, "certificate signed by unknown authority")
}
// quikNeedsTrust — последняя проверка упала на сертификате, а доверие выключено: раскрыть «Дополнительно».
func quikNeedsTrust(s QuikMNPSettings) bool {
return !s.Verified && !s.Trust && strings.Contains(s.LastReport, "Что сделать: сертификат")
}
// testQuik — POST /admin/setup/quik/test. Тест-подключение (только чтение).
// Формирует ПОЛНЫЙ отчёт (параметры + ответ сервера) для копирования коллегам.
func (h *setupHandlers) testQuik(w http.ResponseWriter, r *http.Request) {
@@ -195,10 +248,18 @@ func (h *setupHandlers) testQuik(w http.ResponseWriter, r *http.Request) {
if err != nil {
fmt.Fprintf(&b, "РЕЗУЛЬТАТ: ОШИБКА ПОДКЛЮЧЕНИЯ\n\n")
b.WriteString(quikErrorDetail(err))
cert := quikCertError(err) && !s.Trust
if cert {
b.WriteString("\n\nЧто сделать: " + quikCertHint)
}
s.Verified = false
s.LastReport = b.String()
s.LastAt = now.UTC()
_ = h.rc.UpdateQuik(s)
if cert {
setupFlash(w, r, "QUIK МНП: "+quikCertHint)
return
}
setupFlash(w, r, "QUIK МНП: подключение не удалось — полный ответ сервера ниже (можно скопировать для коллег)")
return
}