Оповещения: группа «Администратор системы», блок «Кому писать» открыт; копии: адрес хранилища как в проводнике

Администратору (адрес из «Почты») — сбои (ИШ, связь с НРД, службы bj, DEPO) и обновления (новый выпуск bj,
ИШ, лицензия, сертификаты); отделам — переводы и поручения. Существующие установки: администратор получает
всё, что получал. Новые оповещения: «Доступно обновление bj» (раз на выпуск), «Служба … не работает /
снова работает» (модули и служба ключей Валидаты). Резервные копии: \\сервер\папка или сервер + общая
папка по полям → //сервер/папка; понятная причина отказа доступа.

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 22:44:35 +03:00
parent f07bb53162
commit d23ddf6e0c
16 changed files with 547 additions and 63 deletions
+71 -13
View File
@@ -110,6 +110,8 @@ type UpdateSettings struct {
// сменили, данные прошлой проверки к новому каналу не относятся: предлагать
// по ним обновление нельзя (кнопка «Обновить» уходила бы в пустоту).
ComponentsChannel string `json:"components_channel,omitempty"`
// Notified — выпуск, о котором уже написали администратору («Доступно обновление bj»): по разу на выпуск.
Notified string `json:"notified,omitempty"`
}
// NewsSettings — лента новостей (события системы, окна техработ, обновления
@@ -358,6 +360,7 @@ func NewRuntimeConfig(path string) (*RuntimeConfig, error) {
if err := rc.load(); err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
migrateNotifyRoutes(&rc.data) // и на новой установке: версия маршрутов сохранится с первой записью настроек
// Гарантируем разумные дефолты.
if rc.data.Crypto.Provider == "" {
rc.data.Crypto.Provider = "stub"
@@ -595,18 +598,25 @@ type IncomingNotifySettings struct {
Emails string `json:"emails"` // депозитарий (через запятую)
EmailsBack string `json:"emails_back"` // бэк-офис
EmailsMiddle string `json:"emails_middle"` // мидл-офис
// Маршрутизация: тип уведомления → группы через запятую (depo,back,middle).
// Типы: incoming (входящие переводы), orders (сроки депо-поручений), quik (QUIK МНП), system (прочие).
// Маршрутизация: тип уведомления → группы через запятую (admin,depo,back,middle). Адрес группы admin
// (администратор системы) — Alerts.SMTP.To. Типы: incoming (входящие переводы), orders (сроки депо-поручений),
// quik (QUIK МНП), infra (сбои шлюза, связи с НРД, служб, DEPO), updates (обновления, лицензия, сертификаты),
// system (прочие).
Routes map[string]string `json:"routes"`
// RoutesV — версия маршрутов: 2 — с группой «Администратор системы» (06.10.2026), см. migrateNotifyRoutes.
RoutesV int `json:"routes_v,omitempty"`
}
// Группы получателей и типы уведомлений (порядок — для формы).
var notifyGroups = []struct{ Key, Title string }{{"depo", "Депозитарий"}, {"back", "Бэк-офис"}, {"middle", "Мидл-офис"}}
// Группы получателей и типы уведомлений (порядок — для формы). Администратор системы — тот, кто отвечает за
// сервер и ИТ: ему сбои и обновления, которые отделам (депозитарий, бэк- и мидл-офис) не нужны.
var notifyGroups = []struct{ Key, Title string }{{"admin", "Администратор"}, {"depo", "Депозитарий"}, {"back", "Бэк-офис"}, {"middle", "Мидл-офис"}}
var notifyKinds = []struct{ Key, Title string }{
{"incoming", "Входящие переводы (получен / принят)"},
{"orders", "Сроки депо-поручений НРД (MS010 5 мин / 30 дней)"},
{"quik", "QUIK МНП (ошибки блокировки / списания / зачисления)"},
{"system", "Прочие системные оповещения"},
{"infra", "Сбои: шлюз НРД, связь с НРД, службы bj, обмен с DEPO"},
{"updates", "Обновления bj и шлюза, лицензия, сертификаты"},
{"system", "Прочие оповещения"},
}
// visibleNotifyKinds — типы уведомлений этой установки: ошибки QUIK МНП — только при модуле в лицензии.
@@ -621,16 +631,18 @@ func visibleNotifyKinds(rc *RuntimeConfig) []struct{ Key, Title string } {
return out
}
// defaultNotifyRoutes — маршруты по умолчанию, пока оператор не настроил свои.
var defaultNotifyRoutes = map[string]string{"incoming": "depo,back", "orders": "depo,back", "quik": "back,middle", "system": "back"}
// defaultNotifyRoutes — маршруты по умолчанию, пока оператор не настроил свои: сбои и обновления —
// администратору, переводы и поручения — отделам.
var defaultNotifyRoutes = map[string]string{"incoming": "depo,back", "orders": "depo,back", "quik": "back,middle",
"infra": "admin", "updates": "admin", "system": "admin,back"}
// RouteHas — получает ли группа уведомления данного типа.
// RouteHas — получает ли группа уведомления данного типа (тип, которого нет в сохранённых маршрутах, — по умолчанию).
func (s IncomingNotifySettings) RouteHas(kind, group string) bool {
r := s.Routes
if r == nil {
r = defaultNotifyRoutes
route, ok := s.Routes[kind]
if !ok {
route = defaultNotifyRoutes[kind]
}
for _, g := range strings.Split(r[kind], ",") {
for _, g := range strings.Split(route, ",") {
if strings.TrimSpace(g) == group {
return true
}
@@ -651,7 +663,44 @@ func (s IncomingNotifySettings) GroupEmails(group string) string {
return ""
}
// Recipients — адреса всех групп, на которые направлен тип уведомления (без дублей).
// migrateNotifyRoutes — переход на маршруты с группой «Администратор системы». До неё адрес из раздела «Почта»
// получал все системные оповещения, а сбои и обновления были «прочими»: на существующей установке администратор
// получает всё то же, что и раньше (лишнее снимается в таблице), сбои и обновления идут тем же группам, что
// «прочие». Галочка «уведомления о переводах» заменена строкой таблицы: выключенная — пустой маршрут входящих.
// Новая установка (ни адреса, ни маршрутов) — маршруты по умолчанию.
func migrateNotifyRoutes(s *Settings) {
in := &s.Incoming
if in.RoutesV >= 2 {
return
}
in.RoutesV = 2
if in.Routes == nil && strings.TrimSpace(s.Alerts.SMTP.To) == "" && !in.Enabled {
in.Enabled = true
return
}
old := in.Routes
if old == nil {
old = map[string]string{"incoming": "depo,back", "orders": "depo,back", "quik": "back,middle", "system": "back"}
}
r := map[string]string{}
for k, v := range old {
r[k] = v
}
r["infra"], r["updates"] = old["system"], old["system"]
if strings.TrimSpace(s.Alerts.SMTP.To) != "" {
for _, k := range []string{"orders", "quik", "infra", "updates", "system"} {
r[k] = strings.Trim(r[k]+",admin", ",")
}
}
if !in.Enabled {
r["incoming"] = ""
}
in.Enabled = true
in.Routes = r
}
// Recipients — адреса отделов (депозитарий, бэк-, мидл-офис), на которые направлен тип уведомления (без дублей);
// с администратором системы — notifyRecipients.
func (s IncomingNotifySettings) Recipients(kind string) string {
seen := map[string]bool{}
var out []string
@@ -671,6 +720,15 @@ func (s IncomingNotifySettings) Recipients(kind string) string {
return strings.Join(out, ", ")
}
// notifyRecipients — все адреса типа уведомления: администратор системы (адрес из «Почты») и отделы.
func notifyRecipients(s Settings, kind string) string {
admin := ""
if s.Incoming.RouteHas(kind, "admin") {
admin = s.Alerts.SMTP.To
}
return mergeEmails(admin, s.Incoming.Recipients(kind))
}
func (r *RuntimeConfig) UpdateIncoming(s IncomingNotifySettings) error {
r.mu.Lock()
r.data.Incoming = s