5beb726e5b
Форма «Загрузка справочника из файла» на /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
140 lines
7.7 KiB
HTML
140 lines
7.7 KiB
HTML
{{define "content"}}
|
|
{{if .Flash}}
|
|
<div class="card" style="border-left:4px solid var(--accent,#2d6cdf)">
|
|
<div style="font-size:14px">{{.Flash}}</div>
|
|
</div>
|
|
{{end}}
|
|
<div class="card">
|
|
<h2>Справочник участников M2M</h2>
|
|
<p class="muted">Актуальная карта участников сервиса MOEX МОСТ (получена от НРД пакетом <code>#M2MTH</code>): ИНН, код участника (MC/EC/…), тип и наименование. Используется для сопоставления контрагента с кодом участника.
|
|
{{if .At}}<br><span style="font-size:12px">Справочник обновлён: <strong>{{.At}}</strong>.</span>{{end}}
|
|
</p>
|
|
{{if not .Participants}}
|
|
<div style="margin-top:12px;padding:14px;background:var(--warn-weak,#f6eed8);border-radius:10px">
|
|
<strong>Справочник ещё не загружен.</strong>
|
|
<p class="muted" style="margin:6px 0 0">Запросите его у НРД: <a href="/admin/setup">Настройка → Диагностика → «Запросить справочник у НРД»</a>. Ответ придёт во входящие ИШ (~30–60 сек) и появится здесь.</p>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 style="margin-top:0">Загрузка справочника из файла</h2>
|
|
<p class="muted" style="margin-top:0">Если шлюз к НРД недоступен, справочник можно загрузить файлом в формате НРД (<code>hk:M2MTransferHandbook</code>, windows-1251 — тот же, что приходит пакетом <code>#M2MTH</code> и который отдаёт кнопка «XML»). Файл заменит текущий справочник, перезапишет таблицу в DEPO и обновит получателей в МНП.</p>
|
|
<form method="post" action="/admin/handbook/import" enctype="multipart/form-data" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
<input type="file" name="handbook" accept=".xml,text/xml,application/xml" required>
|
|
<button class="btn" type="submit">Загрузить</button>
|
|
</form>
|
|
</div>
|
|
|
|
{{if or .Result.Found .Result.Err}}
|
|
<div class="card">
|
|
<h2 style="margin-top:0">Результат обработки в Fancy</h2>
|
|
<p class="muted" style="margin-top:0">Итог сверки справочника юрлиц Fancy с данными НРД (джоба bj2depo, поле COMMENT последнего события HANDBOOK).</p>
|
|
{{if .Result.Err}}
|
|
<p class="muted">Результат пока недоступен: {{.Result.Err}}.</p>
|
|
{{else}}
|
|
<div style="padding:12px 14px;border-radius:10px;background:{{if eq .Result.Severity "err"}}var(--err-weak,#f7dede){{else if eq .Result.Severity "warn"}}var(--warn-weak,#f6eed8){{else}}var(--ok-weak,#e3f2e3){{end}}">
|
|
<div style="font-weight:600;margin-bottom:6px">
|
|
{{if eq .Result.Severity "err"}}⛔ Обнаружены ошибки — проверьте и исправьте{{else if eq .Result.Severity "warn"}}⚠ Есть предупреждения — проверьте{{else}}✓ Справочник обработан, расхождений нет{{end}}
|
|
<span class="muted" style="font-weight:400;font-size:12px">· обработано {{.Result.AtText}}{{with .Result.State}} · статус {{.}}{{end}}</span>
|
|
</div>
|
|
{{with .Result.Comment}}<pre style="white-space:pre-wrap;word-break:break-word;margin:0;font-size:13px;line-height:1.5;font-family:inherit">{{.}}</pre>{{else}}<span class="muted">джоба отработала, но COMMENT пуст</span>{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .Participants}}
|
|
<div class="card">
|
|
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center;justify-content:space-between">
|
|
<div style="flex:1;min-width:220px">
|
|
<input id="hb-search" type="text" placeholder="Поиск по наименованию, ИНН или коду…" autocomplete="off"
|
|
style="width:100%;max-width:520px" oninput="hbFilter()">
|
|
</div>
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<select id="hb-kind" onchange="hbFilter()" style="max-width:200px">
|
|
<option value="">Все типы</option>
|
|
<option value="depo">Депозитарии</option>
|
|
<option value="broker">Брокеры</option>
|
|
<option value="place">Места расчётов</option>
|
|
</select>
|
|
<span class="muted" id="hb-count" style="font-size:13px;white-space:nowrap"></span>
|
|
<a class="btn" href="/admin/handbook/export.json" download title="Скачать справочник в формате JSON">JSON</a>
|
|
<a class="btn" href="/admin/handbook/export.xml" download title="Скачать справочник в формате XML">XML</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tw" style="margin-top:14px">
|
|
<table id="hb-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:150px">Код участника</th>
|
|
<th style="width:140px">ИНН</th>
|
|
<th style="width:120px">Тип</th>
|
|
<th>Наименование</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .Participants}}
|
|
<tr class="hb-row" data-kind="{{.Kind}}"
|
|
data-s="{{.Code}} {{.INN}} {{.Name}}">
|
|
<td class="mono">{{if .Code}}{{.Code}}{{else}}—{{end}}</td>
|
|
<td class="mono">{{.INN}}</td>
|
|
<td>
|
|
{{if eq .Kind "depo"}}<span class="pill p-depo">депозитарий</span>
|
|
{{else if eq .Kind "broker"}}<span class="pill p-broker">брокер</span>
|
|
{{else if eq .Kind "place"}}<span class="pill p-place">место расчётов</span>
|
|
{{else}}{{.Kind}}{{end}}
|
|
</td>
|
|
<td>{{.Name}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="muted" id="hb-empty" style="display:none;margin-top:12px">Ничего не найдено по запросу.</p>
|
|
</div>
|
|
|
|
<style>
|
|
#hb-table td{vertical-align:middle}
|
|
.pill{font-family:var(--mono,monospace);font-size:11px;font-weight:600;padding:3px 9px;border-radius:999px;white-space:nowrap;display:inline-block}
|
|
.p-depo{background:var(--accent-weak,#e8ecfb);color:var(--accent,#1e4fd8)}
|
|
.p-broker{background:var(--warn-weak,#f6eed8);color:var(--warn-ink,#8a5806)}
|
|
.p-place{background:var(--ok-weak,#e2f1eb);color:var(--good-ink,#0c6144)}
|
|
mark{background:var(--accent-weak,#e8ecfb);color:inherit;padding:0 1px;border-radius:3px}
|
|
</style>
|
|
<script>
|
|
(function(){
|
|
var rows=[].slice.call(document.querySelectorAll('.hb-row'));
|
|
window.hbFilter=function(){
|
|
var q=(document.getElementById('hb-search').value||'').trim().toLowerCase();
|
|
var kind=document.getElementById('hb-kind').value;
|
|
var shown=0;
|
|
rows.forEach(function(r){
|
|
var s=(r.getAttribute('data-s')||'').toLowerCase();
|
|
var okQ=!q || s.indexOf(q)>=0;
|
|
var okK=!kind || r.getAttribute('data-kind')===kind;
|
|
var vis=okQ&&okK;
|
|
r.style.display=vis?'':'none';
|
|
if(vis)shown++;
|
|
});
|
|
document.getElementById('hb-count').textContent='Показано: '+shown+' из '+rows.length;
|
|
document.getElementById('hb-empty').style.display=shown?'none':'block';
|
|
};
|
|
hbFilter();
|
|
// Enter в поиске не отправляет форму
|
|
var inp=document.getElementById('hb-search');
|
|
inp.addEventListener('keydown',function(e){if(e.key==='Enter')e.preventDefault();});
|
|
inp.focus();
|
|
})();
|
|
</script>
|
|
{{end}}
|
|
|
|
<div class="card">
|
|
<details>
|
|
<summary style="cursor:pointer;font-weight:600;font-size:15px">Коды ошибок и отказов M2M — справка</summary>
|
|
<div style="margin-top:10px">{{.CodesHTML}}</div>
|
|
</details>
|
|
</div>
|
|
{{end}}
|