feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
package lkgateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// registerQuik — маршруты вкладки «QUIK МНП» (подключение к БД Модуля
|
||||
// неторговых поручений). Только сохранение реквизитов и тест-подключение
|
||||
// (read-only). БД боевая — запись не делаем.
|
||||
func (h *setupHandlers) registerQuik(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/admin/setup/quik", h.saveQuik)
|
||||
mux.HandleFunc("/admin/setup/quik/test", h.testQuik)
|
||||
mux.HandleFunc("/admin/quik", h.quikInstructions)
|
||||
mux.HandleFunc("/admin/setup/quik/cycle", h.saveQuikCycle)
|
||||
}
|
||||
|
||||
// quikInstructions — GET /admin/quik. Поручения M2M из БД МНП QUIK.
|
||||
// ТОЛЬКО ЧТЕНИЕ (боевая БД): SELECT из экспортной таблицы Export.
|
||||
func (h *setupHandlers) quikInstructions(w http.ResponseWriter, r *http.Request) {
|
||||
s := h.rc.Snapshot().Quik
|
||||
onlyActive := r.URL.Query().Get("all") == ""
|
||||
var items []QuikInstruction
|
||||
var loadErr string
|
||||
if s.Server == "" {
|
||||
loadErr = "Подключение к QUIK не настроено — Настройка → QUIK МНП"
|
||||
} else if list, err := quikListM2MInstructions(r.Context(), s, 200, onlyActive); err != nil {
|
||||
loadErr = "не удалось прочитать поручения: " + err.Error()
|
||||
} else {
|
||||
items = list
|
||||
}
|
||||
data := struct {
|
||||
page
|
||||
Items []QuikInstruction
|
||||
Error string
|
||||
OnlyActive bool
|
||||
DBName string
|
||||
Links []*QuikLink
|
||||
Cycle QuikMNPSettings
|
||||
Flash string
|
||||
}{nowPage("Поручения QUIK", "quik"), items, loadErr, onlyActive, s.Database, globalQuikLinks.List(100), s, r.URL.Query().Get("flash")}
|
||||
render(w, h.tpl.a.quikInstr, data)
|
||||
}
|
||||
|
||||
func (h *setupHandlers) quikFromForm(r *http.Request) QuikMNPSettings {
|
||||
cur := h.rc.Snapshot().Quik
|
||||
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
|
||||
if port == 0 {
|
||||
port = cur.Port
|
||||
}
|
||||
pass := r.FormValue("password")
|
||||
if pass == "" {
|
||||
pass = cur.Password // не затираем сохранённый пароль пустым полем
|
||||
}
|
||||
enc := strings.TrimSpace(r.FormValue("encrypt"))
|
||||
if enc == "" {
|
||||
enc = "true"
|
||||
}
|
||||
return QuikMNPSettings{
|
||||
Server: strings.TrimSpace(r.FormValue("server")),
|
||||
Port: port,
|
||||
Database: strings.TrimSpace(r.FormValue("database")),
|
||||
User: strings.TrimSpace(r.FormValue("user")),
|
||||
Password: pass,
|
||||
Encrypt: enc,
|
||||
Trust: r.FormValue("trust") != "",
|
||||
Verified: cur.Verified,
|
||||
// поля автоматического цикла M2M правятся отдельной формой — не затираем
|
||||
WritesEnabled: cur.WritesEnabled, AutoOut: cur.AutoOut, AutoIn: cur.AutoIn, PollTerminal: cur.PollTerminal,
|
||||
Account: cur.Account, SecOut: cur.SecOut, SecIn: cur.SecIn, ClientCodeMap: cur.ClientCodeMap,
|
||||
}
|
||||
}
|
||||
|
||||
// saveQuikCycle — POST /admin/setup/quik/cycle. Настройки автоматического цикла M2M в МНП.
|
||||
func (h *setupHandlers) saveQuikCycle(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
|
||||
s.WritesEnabled = r.FormValue("writes_enabled") != ""
|
||||
s.AutoOut = r.FormValue("auto_out") != ""
|
||||
s.AutoIn = r.FormValue("auto_in") != ""
|
||||
s.PollTerminal = r.FormValue("poll_terminal") != ""
|
||||
s.Account = strings.TrimSpace(r.FormValue("account"))
|
||||
s.SecOut = strings.TrimSpace(r.FormValue("sec_out"))
|
||||
s.SecIn = strings.TrimSpace(r.FormValue("sec_in"))
|
||||
m := map[string]string{}
|
||||
for _, line := range strings.Split(r.FormValue("client_code_map"), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
if len(kv) == 2 && strings.TrimSpace(kv[0]) != "" {
|
||||
m[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
|
||||
}
|
||||
}
|
||||
s.ClientCodeMap = m
|
||||
if err := h.rc.UpdateQuik(s); err != nil {
|
||||
setupFlash(w, r, "QUIK МНП: ошибка сохранения: "+err.Error())
|
||||
return
|
||||
}
|
||||
mode := "записи в МНП ВЫКЛЮЧЕНЫ"
|
||||
if s.WritesEnabled {
|
||||
mode = fmt.Sprintf("записи включены: исходящие %s, входящие %s, поручения из терминала %s", onOff(s.AutoOut), onOff(s.AutoIn), onOff(s.PollTerminal))
|
||||
}
|
||||
setupFlash(w, r, "QUIK МНП: цикл M2M сохранён — "+mode)
|
||||
}
|
||||
|
||||
func onOff(b bool) string {
|
||||
if b {
|
||||
return "авто"
|
||||
}
|
||||
return "вручную"
|
||||
}
|
||||
|
||||
// quikClientCodeMapText — карта исключений в виде «ключ=значение» построчно (для формы).
|
||||
func quikClientCodeMapText(m map[string]string) string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var b strings.Builder
|
||||
for _, k := range keys {
|
||||
b.WriteString(k + "=" + m[k] + "\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// saveQuik — POST /admin/setup/quik. Сохраняет реквизиты подключения.
|
||||
func (h *setupHandlers) saveQuik(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.quikFromForm(r)
|
||||
if s.Server == "" || s.User == "" {
|
||||
setupFlash(w, r, "QUIK МНП: укажите адрес сервера и логин")
|
||||
return
|
||||
}
|
||||
if err := h.rc.UpdateQuik(s); err != nil {
|
||||
setupFlash(w, r, "QUIK МНП: ошибка сохранения: "+err.Error())
|
||||
return
|
||||
}
|
||||
setupFlash(w, r, fmt.Sprintf("QUIK МНП: настройки сохранены (%s:%d, логин %s)", s.Server, s.Port, s.User))
|
||||
}
|
||||
|
||||
// testQuik — POST /admin/setup/quik/test. Тест-подключение (только чтение).
|
||||
// Формирует ПОЛНЫЙ отчёт (параметры + ответ сервера) для копирования коллегам.
|
||||
func (h *setupHandlers) testQuik(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
s := h.rc.Snapshot().Quik
|
||||
if s.Server == "" {
|
||||
setupFlash(w, r, "QUIK МНП: сначала сохраните реквизиты подключения")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "=== Тест подключения к БД МНП QUIK ===\n")
|
||||
fmt.Fprintf(&b, "Время: %s\n", now.Format("2006-01-02 15:04:05 MST"))
|
||||
fmt.Fprintf(&b, "Сервер: %s:%d\n", s.Server, s.Port)
|
||||
fmt.Fprintf(&b, "БД: %s\n", dfltDB(s.Database))
|
||||
fmt.Fprintf(&b, "Логин: %s\n", s.User)
|
||||
fmt.Fprintf(&b, "Шифрование: encrypt=%s, TrustServerCertificate=%v\n", s.Encrypt, s.Trust)
|
||||
fmt.Fprintf(&b, "Клиент (bj): исходящий IP 195.239.71.22, драйвер go-mssqldb\n")
|
||||
fmt.Fprintf(&b, "----------------------------------------\n")
|
||||
|
||||
res, err := quikProbe(r.Context(), s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&b, "РЕЗУЛЬТАТ: ОШИБКА ПОДКЛЮЧЕНИЯ\n\n")
|
||||
b.WriteString(quikErrorDetail(err))
|
||||
s.Verified = false
|
||||
s.LastReport = b.String()
|
||||
s.LastAt = now.UTC()
|
||||
_ = h.rc.UpdateQuik(s)
|
||||
setupFlash(w, r, "QUIK МНП: подключение не удалось — полный ответ сервера ниже (можно скопировать для коллег)")
|
||||
return
|
||||
}
|
||||
// успех
|
||||
fmt.Fprintf(&b, "РЕЗУЛЬТАТ: ПОДКЛЮЧЕНИЕ OK\n\n")
|
||||
fmt.Fprintf(&b, "Версия сервера: %s\n", res.Version)
|
||||
fmt.Fprintf(&b, "Текущая БД: %s\n", res.DBName)
|
||||
fmt.Fprintf(&b, "Логин на серв.: %s\n", res.Login)
|
||||
fmt.Fprintf(&b, "M2M-готовность: %v — %s\n\n", res.M2MReady, res.Note)
|
||||
fmt.Fprintf(&b, "Объекты МНП/M2M (видимость под логином):\n")
|
||||
keys := make([]string, 0, len(res.Objects))
|
||||
for k := range res.Objects {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
mark := "нет"
|
||||
if res.Objects[k] {
|
||||
mark = "ДА"
|
||||
}
|
||||
fmt.Fprintf(&b, " %-28s %s\n", k, mark)
|
||||
}
|
||||
s.Verified = true
|
||||
s.LastReport = b.String()
|
||||
s.LastAt = now.UTC()
|
||||
_ = h.rc.UpdateQuik(s)
|
||||
setupFlash(w, r, fmt.Sprintf("QUIK МНП: ПОДКЛЮЧЕНИЕ OK (БД=%s). Полный отчёт ниже.", res.DBName))
|
||||
}
|
||||
|
||||
func dfltDB(db string) string {
|
||||
if db == "" {
|
||||
return "(по умолчанию для логина)"
|
||||
}
|
||||
return db
|
||||
}
|
||||
Reference in New Issue
Block a user