package lkgateway // Мастер настройки — главы вместо длинного списка шагов (05.10.2026). Внутри глав — прежние шаги (адреса ?step=N не // меняются: на них ссылаются проверка готовности, письма, кнопки «Исправить»). Шаг показывается, только // если он нужен этой установке: // - не входит в лицензию (учёт DEPO и QUIK — «quik-mnp», модули) — шага нет вовсе; // - сделан установщиком без участия человека (база bj, Валидата) — шага нет в пути, в главе «Сервер» // это показано строкой «сделано автоматически». import "strings" // wizChapters — главы мастера: название и шаги по порядку. var wizChapters = []struct { Title string Steps []int }{ {"Сервер", []int{1, 14, 2, 6, 12}}, // обновление — сразу после лицензии: без неё обновлений нет {"Учёт и организация", []int{3, 4, 5}}, {"НРД", []int{7, 8}}, {"Запуск", []int{11, 9, 13, 10}}, } // wizStepTitles — короткие названия шагов (подзаголовки внутри главы). var wizStepTitles = map[int]string{ 1: "Лицензия", 2: "База данных bj", 6: "СКЗИ «Валидата»", 12: "Время и сеть", 14: "Обновление", 3: "Учёт DEPO (Fancy)", 4: "Организация", 5: "QUIK МНП", 7: "Шлюз НРД", 8: "Ключ подписи", 11: "Модули", 9: "Оповещения и копии", 13: "Учётные записи", 10: "Проверка", } // wizRequired — шаги, без которых обмен не работает: на них мастер возвращает, пока они не сделаны. var wizRequired = map[int]bool{1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true} // wizModules — лицензируемые модули, которые настраиваются в главе «Запуск». var wizModules = []string{"web-cabinet", "esia-finance", "quik-export", "npr", "onec-back"} // WizStep — шаг в пути мастера. type WizStep struct { N int // номер шага в адресе (?step=N) Num int // порядковый номер в списке шагов слева (1, 2, 3… — только шаги этой установки) Title string Done bool Current bool } // WizChapter — глава в шапке мастера. type WizChapter struct { N int Title string Steps []WizStep Done bool Current bool First int // первый шаг главы (переход по клику) } // wizVisible — нужен ли шаг этой установке (лицензия и автоматика). func wizVisible(rc *RuntimeConfig, n int, done map[int]bool) bool { switch n { case 2, 6: // база bj и Валидата обычно ставятся установщиком — шаг нужен, только если не сделано return !done[n] case 3, 5: return moduleAllowed(rc, "quik-mnp") case 11: for _, m := range wizModules { if moduleAllowed(rc, m) { return true } } return false } return true } // wizPath — шаги этой установки по порядку и главы для шапки. func wizPath(rc *RuntimeConfig, done map[int]bool, current int) (path []int, chapters []WizChapter) { for ci, ch := range wizChapters { c := WizChapter{N: ci + 1, Title: ch.Title, Done: true} for _, n := range ch.Steps { if !wizVisible(rc, n, done) { continue } path = append(path, n) c.Steps = append(c.Steps, WizStep{N: n, Num: len(path), Title: wizStepTitles[n], Done: done[n], Current: n == current}) if c.First == 0 { c.First = n } if !done[n] { c.Done = false } if n == current { c.Current = true } } if len(c.Steps) == 0 { continue } if len(c.Steps) == 1 { // в главе один шаг (остальные не в лицензии) — глава называется по нему: «Организация» c.Title = c.Steps[0].Title } // переход по главе — на первый несделанный шаг главы for _, s := range c.Steps { if !s.Done { c.First = s.N break } } chapters = append(chapters, c) } return path, chapters } // wizChaptersLine — подзаголовок мастера: «Четыре главы: сервер, организация, НРД, запуск». func wizChaptersLine(chapters []WizChapter) string { words := []string{"", "Одна глава", "Две главы", "Три главы", "Четыре главы", "Пять глав"} var names []string for _, c := range chapters { t := c.Title if t != "НРД" && t != "" { r := []rune(t) t = strings.ToLower(string(r[:1])) + string(r[1:]) } names = append(names, t) } if len(chapters) == 0 || len(chapters) >= len(words) { return "" } return words[len(chapters)] + ": " + strings.Join(names, ", ") + "." } // wizNeighbours — предыдущий и следующий шаг в пути (0 — нет). func wizNeighbours(path []int, current int) (prev, next int) { for i, n := range path { if n == current { if i > 0 { prev = path[i-1] } if i+1 < len(path) { next = path[i+1] } return } } // текущий шаг не в пути (сделан установщиком, открыт по прямой ссылке) — соседи по порядку глав, // а не начало мастера pos := map[int]int{} for _, c := range wizChapters { for _, n := range c.Steps { pos[n] = len(pos) } } cur, known := pos[current] if !known { if len(path) > 0 { next = path[0] } return } for _, n := range path { if pos[n] < cur { prev = n } else if next == 0 { next = n } } return } // wizDefaultStep — куда открывается мастер без ?step: первый обязательный несделанный шаг, иначе проверка. func wizDefaultStep(path []int, done map[int]bool) int { for _, n := range path { if wizRequired[n] && !done[n] { return n } } return 10 } // WizModule — модуль по лицензии в главе «Запуск»: что делает, готов ли, где настраивается. type WizModule struct { Key, Title, What, Status, Link string Ready bool Deferred bool // «Настроить позже»: мастер считает модуль завершённым } // wizModuleViews — модули этой лицензии (без лицензии модуля в мастере нет). func wizModuleViews(rc *RuntimeConfig, s Settings) []WizModule { var out []WizModule for _, key := range wizModules { if !moduleAllowed(rc, key) { continue } m := WizModule{Key: key} switch key { case "web-cabinet": m.Title = "Личный кабинет клиента" m.What = "Наш кабинет для клиентов депозитария: программа bj-lk на веб-сервере организации — клиент подаёт поручения на перевод, видит портфель и ход перевода." m.Link = "/admin/setup?from=wizard#cabinet" m.Ready = s.LK.APIKey != "" m.Status = map[bool]string{true: "ключ кабинета выдан — кабинет подключается к bj", false: "кабинет ещё не установлен: скачайте дистрибутив и ключ в разделе модуля"}[m.Ready] case "esia-finance": m.Title = "ЕСИА Финанс" m.What = "Интеграция с платформой личного кабинета ЕСИА Финанс: заявки клиентов на перевод приходят в bj, статусы и портфель уходят обратно." m.Link = "/admin/setup?from=wizard#lk" m.Ready = s.LK.PublicURL != "" && (s.LK.Prod.LastOK || s.LK.Test.LastOK) switch { case m.Ready: m.Status = "адрес кабинета задан, связь с API проверена" case s.LK.PublicURL != "": m.Status = "адрес кабинета задан, связь с API ещё не проверена" default: m.Status = "не настроено: адрес кабинета и доступ к API" } case "quik-export": m.Title = "Экспорт биржевой информации QUIK" m.What = "Выгрузка данных QUIK (позиции, сделки, клиенты) в базу bj — для кабинета клиента и риск-монитора." m.Link = "/admin/setup?from=wizard#quikexport" m.Ready = unitState("bj-quikexport").State == "active" m.Status = map[bool]string{true: "служба экспорта работает", false: "служба экспорта не запущена — настройте подключение в разделе модуля"}[m.Ready] case "npr": m.Title = "Риск-монитор НПР / УДС" m.What = "Нормативы покрытия риска и уровень достаточности средств клиентов маржинальной торговли по данным экспорта QUIK." m.Link = "/admin/setup?from=wizard#quikexport" m.Ready = s.NPR.Enabled && s.NPR.DSN != "" m.Status = map[bool]string{true: "включён, база экспорта QUIK подключена", false: "не включён"}[m.Ready] case "onec-back": m.Title = "1С: база бэк-офиса" m.What = "Связь с базой 1С бэк-офиса (модуль в разработке): пока — подключение к опубликованной базе и его проверка." m.Link = "/admin/setup?from=wizard#onec" m.Ready = s.OneC.CheckOK switch { case m.Ready: m.Status = "подключение к базе 1С проверено" case s.OneC.URL != "": m.Status = "адрес базы задан, проверка не прошла" default: m.Status = "не настроено: адрес публикации базы, пользователь и пароль 1С" } } out = append(out, m) } return out }