Files
Bridge-and-Join-s/internal/lkgateway/handbook_import.go
T
fontvielle 5beb726e5b Справочник M2M: ручная загрузка файла в формате НРД
Форма «Загрузка справочника из файла» на /admin/handbook принимает
подлинный формат НРД (hk:M2MTransferHandbook, windows-1251 — тот же,
что приходит пакетом #M2MTH и который отдаёт кнопка «XML»). Разбор и
сохранение идут тем же путём, что у поллера при ответе НРД:
nsdxml.Unmarshal → writeHandbookRawXML (сырой XML для байт-в-байт
выгрузки) → saveHandbookEx (плоский JSON) → перезапись FTV_M2M_HANDBOOK
в DEPO → синхронизация получателей в МНП. На сервер лицензий ручной файл
НЕ рассылается (saveHandbookEx push=false): справочник одного стенда не
должен становиться облачным для всех клиентов. Нужно для закрытого
контура и когда шлюз к НРД недоступен.

- handbook_import.go: обработчик importHandbook (POST /admin/handbook/import)
- nsdpoller.go: saveHandbook → saveHandbookEx(hb, push)
- admin.go: маршрут import, поле Flash, renderHandbook читает ?flash=
- admin_handbook.html: карточка загрузки + вывод flash

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XG3zNMMf2LMoDpXo22mwMX
2026-10-15 11:18:50 +03:00

84 lines
4.2 KiB
Go

package lkgateway
// handbook_import.go — ручная загрузка справочника участников M2M со страницы
// /admin/handbook файлом в ПОДЛИННОМ формате НРД (hk:M2MTransferHandbook,
// windows-1251 — тот же, что НРД присылает пакетом #M2MTH и который отдаёт
// кнопка «XML»). Нужна для закрытого контура и когда шлюз к НРД недоступен.
//
// Обрабатываем ровно тем же путём, что и поллер при ответе НРД (nsdpoller.go):
// writeHandbookRawXML → saveHandbookEx(push=false) → перезапись FTV_M2M_HANDBOOK
// в DEPO → синхронизация получателей в МНП.
// На сервер лицензий файл НЕ рассылаем: ручная загрузка одного стенда не должна
// становиться облачным справочником для всех клиентов (push=false). Отметку живой
// связи с НРД тоже не ставим — это действие человека, а не ответ шлюза.
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
)
// handbookUploadMaxBytes — предел размера загружаемого файла. Справочник НРД —
// сотни КБ; с запасом.
const handbookUploadMaxBytes = 16 << 20
// importHandbook — POST /admin/handbook/import: принять файл справочника в формате НРД.
func (a *admin) importHandbook(w http.ResponseWriter, r *http.Request) {
back := func(msg string) {
http.Redirect(w, r, "/admin/handbook?flash="+url.QueryEscape(msg), http.StatusSeeOther)
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
r.Body = http.MaxBytesReader(w, r.Body, handbookUploadMaxBytes)
f, hdr, err := r.FormFile("handbook")
if err != nil {
back("Загрузка справочника: файл не получен (" + err.Error() + ")")
return
}
defer f.Close()
raw, err := io.ReadAll(f)
if err != nil {
back("Загрузка справочника: не удалось прочитать файл: " + err.Error())
return
}
if len(raw) == 0 {
back("Загрузка справочника: файл пустой")
return
}
// Разбираем тем же парсером НРД, что и ответ шлюза (поддерживает windows-1251 по прологу).
var hb m2m.M2MTransferHandbook
if err := nsdxml.Unmarshal(raw, &hb); err != nil {
back("Загрузка справочника: это не справочник НРД (hk:M2MTransferHandbook) или файл повреждён: " + err.Error())
return
}
nP, nPl := len(hb.Participants.Participants), len(hb.SettlementPlaces.Places)
if nP == 0 {
back("Загрузка справочника: в файле нет ни одного участника — проверьте, тот ли это файл")
return
}
writeHandbookRawXML(raw) // подлинный XML — для выгрузки кнопкой «XML» байт в байт
changed := saveHandbookEx(&hb, false) // плоский JSON; на сервер лицензий НЕ рассылаем
if globalRC != nil {
go func(rc *RuntimeConfig) {
if err := depoWriteHandbook(rc); err != nil {
log.Printf("depo-events: справочник после ручной загрузки: %v", err)
}
}(globalRC)
quikRecipientsSyncAsync(globalRC) // депозитарии-получатели в МНП
}
note := ""
if !changed {
note = " (состав не изменился)"
}
log.Printf("lk-gateway: справочник M2M загружен ВРУЧНУЮ из файла %q: участников %d, мест расчётов %d, изменился=%v",
hdr.Filename, nP, nPl, changed)
back(fmt.Sprintf("Справочник загружен из файла %s: участников %d, мест расчётов %d%s.", hdr.Filename, nP, nPl, note))
}