diff --git a/internal/lkgateway/admin.go b/internal/lkgateway/admin.go
index 28bb043..067e265 100644
--- a/internal/lkgateway/admin.go
+++ b/internal/lkgateway/admin.go
@@ -566,6 +566,10 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts
a.renderClaim(w, r, svc, id)
case p == "status":
a.renderStatus(w, r, getOpts())
+ case p == "handbook/export.json":
+ a.exportHandbook(w, "json")
+ case p == "handbook/export.xml":
+ a.exportHandbook(w, "xml")
case p == "handbook":
a.renderHandbook(w, r)
case p == "help":
diff --git a/internal/lkgateway/handbook_export.go b/internal/lkgateway/handbook_export.go
new file mode 100644
index 0000000..cd3b80f
--- /dev/null
+++ b/internal/lkgateway/handbook_export.go
@@ -0,0 +1,87 @@
+package lkgateway
+
+// handbook_export.go — выгрузка справочника участников M2M со страницы
+// /admin/handbook в файлах JSON и XML. Данные — самый свежий справочник
+// (readHandbook): полученный от НРД #M2MTH, копия с сервера лицензий или
+// встроенный снимок. Только чтение, доступ — под админ-авторизацией /admin/*.
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "net/http"
+ "time"
+)
+
+// hbEntryXML — запись справочника в XML (атрибутами, как плоский JSON).
+type hbEntryXML struct {
+ INN string `xml:"inn,attr,omitempty"`
+ Code string `xml:"code,attr,omitempty"`
+ Kind string `xml:"kind,attr,omitempty"`
+ Name string `xml:"name,attr,omitempty"`
+ FullName string `xml:"fullName,attr,omitempty"`
+ DisplayName string `xml:"displayName,attr,omitempty"`
+ EngName string `xml:"engName,attr,omitempty"`
+}
+
+// hbExportXML — корень XML-выгрузки справочника.
+type hbExportXML struct {
+ XMLName xml.Name `xml:"M2MHandbook"`
+ At string `xml:"at,attr,omitempty"`
+ Participants []hbEntryXML `xml:"Participants>Participant"`
+ Places []hbEntryXML `xml:"SettlementPlaces>Place"`
+}
+
+func hbEntryToXML(e HandbookEntry) hbEntryXML {
+ return hbEntryXML{INN: e.INN, Code: e.Code, Kind: e.Kind, Name: e.Name,
+ FullName: e.FullName, DisplayName: e.DisplayName, EngName: e.EngName}
+}
+
+// exportHandbook отдаёт сохранённый справочник участников M2M файлом JSON или XML.
+func (a *admin) exportHandbook(w http.ResponseWriter, format string) {
+ raw, err := readHandbook()
+ if err != nil || len(raw) == 0 {
+ http.Error(w, "Справочник ещё не загружен — запросите его у НРД.", http.StatusNotFound)
+ return
+ }
+ var hb struct {
+ At string `json:"at"`
+ Participants []HandbookEntry `json:"participants"`
+ Places []HandbookEntry `json:"settlement_places"`
+ }
+ if err := json.Unmarshal(raw, &hb); err != nil {
+ http.Error(w, "Справочник повреждён: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ day := time.Now().Format("2006-01-02")
+ if format == "xml" {
+ doc := hbExportXML{At: hb.At}
+ for _, p := range hb.Participants {
+ doc.Participants = append(doc.Participants, hbEntryToXML(p))
+ }
+ for _, p := range hb.Places {
+ doc.Places = append(doc.Places, hbEntryToXML(p))
+ }
+ out, err := xml.MarshalIndent(doc, "", " ")
+ if err != nil {
+ http.Error(w, "Не удалось сформировать XML: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+ w.Header().Set("Content-Disposition", `attachment; filename="m2m-handbook-`+day+`.xml"`)
+ w.Header().Set("Cache-Control", "no-store")
+ _, _ = w.Write([]byte(xml.Header))
+ _, _ = w.Write(out)
+ return
+ }
+ // JSON — отдаём сохранённый справочник как есть, с отступами для читаемости.
+ var buf bytes.Buffer
+ if json.Indent(&buf, raw, "", " ") != nil {
+ buf.Reset()
+ buf.Write(raw)
+ }
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ w.Header().Set("Content-Disposition", `attachment; filename="m2m-handbook-`+day+`.json"`)
+ w.Header().Set("Cache-Control", "no-store")
+ _, _ = w.Write(buf.Bytes())
+}
diff --git a/internal/lkgateway/web/templates/admin_handbook.html b/internal/lkgateway/web/templates/admin_handbook.html
index 3f1fb14..6e022e9 100644
--- a/internal/lkgateway/web/templates/admin_handbook.html
+++ b/internal/lkgateway/web/templates/admin_handbook.html
@@ -45,6 +45,8 @@
+ JSON
+ XML