Раздел «Ручной ввод M2M» по бумажным поручениям; тестовые тексты и имитации убраны из клиентского интерфейса

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-17 18:26:59 +03:00
parent 627dbca6af
commit e4de1ab4d8
38 changed files with 2014 additions and 132 deletions
@@ -58,7 +58,7 @@
{{end}}
</div>
{{with .Claim.TestResult}}
{{if .DevMode}}{{with .Claim.TestResult}}
<div class="card" style="background:{{if eq .Status "ok"}}var(--ok-weak){{else if eq .Status "mismatch"}}var(--err-weak){{else if eq .Status "error"}}var(--err-weak){{else}}var(--warn-weak){{end}}">
<h2>
{{if eq .Status "ok"}}<span style="color:var(--ok)">✓ Тест пройден</span>
@@ -73,7 +73,7 @@
{{if .Note}}<tr><td class="muted">Вывод</td><td>{{.Note}}</td></tr>{{end}}
</table>
</div>
{{end}}
{{end}}{{end}}
<div class="card">
<h2>Ценные бумаги ({{len .Claim.Securities}})</h2>
@@ -235,7 +235,7 @@
{{if .Claim.RequestRawXML}}
<div class="card">
<h2>Отправленный пакет (M2MTransferRequest)</h2>
<p class="muted">Точный XML, который bj-server сформировал и ИШ подписал ключом и отправил в НРД. Тип пакета <code>#M2MTR</code>, канал TEST3.</p>
<p class="muted">Точный XML, который bj-server сформировал и ИШ подписал ключом и отправил в НРД. Тип пакета <code>#M2MTR</code>.</p>
<a class="btn btn-ok" href="/admin/claims/{{.Claim.ID}}/request.xml" download style="margin-right:8px">⤓ Скачать XML запроса</a>
<button type="button" class="btn" onclick="copyReq(this)">Скопировать</button>
<pre id="raw-request" style="white-space:pre-wrap;word-break:break-all;background:var(--surface-2,#f5f5f7);padding:12px;border-radius:8px;font-size:12px;overflow:auto;max-height:340px">{{.Claim.RequestRawXML}}</pre>
@@ -68,7 +68,7 @@
</table>
</div>
{{else if not .Error}}
<p class="muted">В трансферных таблицах пока нет переводов. Они появятся при входящем/исходящем переводе M2M или при прогоне «Тестирование M2M → выгрузка истории в DEPO».</p>
<p class="muted">В трансферных таблицах пока нет переводов. Они появятся при входящем/исходящем переводе M2M{{if .DevMode}} или при прогоне «Тестирование M2M → выгрузка истории в DEPO»{{end}}.</p>
{{end}}
</div>
@@ -7,7 +7,7 @@
{{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>
<p class="muted" style="margin:6px 0 0">Запросите его у НРД: <a href="/admin/setup">Настройка → Диагностика → «Запросить справочник у НРД»</a>. Ответ придёт во входящие ИШ (~30–60 сек) и появится здесь.</p>
</div>
{{end}}
</div>
@@ -20,13 +20,19 @@
<a href="/admin/help/crypto" style="text-decoration:none">
<div class="card" style="height:100%">
<h2 style="color:var(--accent)">Криптография (Валидата) →</h2>
<p class="muted">Установка АПК «Валидата Клиент L» на Astra Linux SE, подключение через PKCS#11, тестирование подписи и проверки квитанций НРД.</p>
<p class="muted">Установка АПК «Валидата Клиент L» на Astra Linux SE, подключение через PKCS#11, проверка подписи и квитанций НРД.</p>
</div>
</a>
<a href="/admin/help/systems" style="text-decoration:none">
<div class="card" style="height:100%">
<h2 style="color:var(--accent)">Внешние системы →</h2>
<p class="muted">ИШ НРД (профили GUEST/TEST3/PROD), команда Fansy (ETL в staging), уведомления (e-mail, Yandex Messenger, Telegram), порядок согласования.</p>
<p class="muted">ИШ НРД ({{if .DevMode}}профили GUEST/TEST3/PROD{{else}}тестовый и боевой контуры{{end}}), команда Fansy (ETL в staging), уведомления (e-mail, Yandex Messenger, Telegram), порядок согласования.</p>
</div>
</a>
<a href="/admin/help/manual" style="text-decoration:none">
<div class="card" style="height:100%">
<h2 style="color:var(--accent)">Ручной ввод M2M →</h2>
<p class="muted">Исходящий перевод по бумажному поручению клиента: ввод, сверка с учётной и торговой системами, обязательные отметки, отправка, поручение 16, документы НРД.</p>
</div>
</a>
{{if .DevMode}}<a href="/admin/help/robot" style="text-decoration:none">
@@ -38,7 +44,7 @@
<a href="/admin/help/architecture" style="text-decoration:none">
<div class="card" style="height:100%">
<h2 style="color:var(--accent)">Архитектура обмена с НРД →</h2>
<p class="muted">Полная схема: bj-server → ИШ (на Astra Linux ВМ) → ONYX (НРД) → робот-автотест. Кто на чьей стороне, какое СКЗИ, какие сертификаты, FAQ.</p>
<p class="muted">Полная схема: bj-server → ИШ (на Astra Linux ВМ) → ONYX (НРД) → {{if .DevMode}}робот-автотест{{else}}депозитарий-контрагент{{end}}. Кто на чьей стороне, какое СКЗИ, какие сертификаты, FAQ.</p>
</div>
</a>
<a href="/admin/help/fancy-events" style="text-decoration:none">
@@ -21,7 +21,7 @@
│ │ • стейт-машина │ │ Делает САМ: │ │
│ │ • PostgreSQL │ │ • подпись │ │
│ │ • админка :8080 │ │ • упаковку ЭДО │ │
│ │ • lk-emulator │ │ • проверку │ │
│ │ {{if .DevMode}}• lk-emulator {{else}}• коннектор ЛК {{end}}│ │ • проверку │ │
│ │ │ │ подписей НРД │ │
│ └──────────────────┘ │ • БД PostgreSQL │ │
│ │ (history) │ │
@@ -46,9 +46,9 @@
│ /onyxpr/WslService │
│ │
│ ↓ внутрь НРД │
│ • робот-автотест │
{{if .DevMode}} │ • робот-автотест │
│ MC0012500000 │
│ • реальные депозитарии │
{{end}} │ • реальные депозитарии │
│ • биржевые системы │
└────────────────────────────┘
</pre>
@@ -64,7 +64,7 @@
<td>наша</td>
<td>Astra Linux SE 1.7 / Linux</td>
<td>АПК «Валидата Клиент L» (PKCS#11)</td>
<td>Стейт-машина, журнал в БД, веб-админка, lk-emulator</td>
<td>Стейт-машина, журнал в БД, веб-админка{{if .DevMode}}, lk-emulator{{end}}</td>
</tr>
<tr>
<td><strong>ИШ (igate)</strong></td>
@@ -80,13 +80,13 @@
<td>—</td>
<td>Web-сервис НРД — принимает пакеты от ИШ всех клиентов</td>
</tr>
<tr>
{{if .DevMode}}<tr>
<td><strong>Робот-автотест</strong></td>
<td>НРД</td>
<td>—</td>
<td>—</td>
<td>Контрагент-эмулятор внутри НРД. Адресуется кодом <code>MC0012500000</code> в TEST3</td>
</tr>
</tr>{{end}}
</tbody>
</table>
</div>
@@ -109,6 +109,7 @@
<h3>Q: Какой сертификат нужен?</h3>
<p>Только от <strong>УЦ Московской Биржи</strong> (<code>ca.moex.com</code>). Сертификаты других УЦ ИШ не примет. УЦ МБ выпускает сертификаты только для организаций, подключённых к ЭДО НРД (по договору).</p>
{{if .DevMode}}
<h3>Q: Что делать, чтобы протестировать на роботе НРД на TEST3?</h3>
<ol>
<li>Получить сертификат УЦ МБ для нашей организации.</li>
@@ -121,8 +122,18 @@
<h3>Q: Сколько времени нужно от старта подключения до прогона на TEST3?</h3>
<p>Оптимистично — <strong>1-2 недели</strong> (если все ответы НРД быстрые и УЦ МБ не задерживает). Реалистично — <strong>3-4 недели</strong>. На нашей стороне всё уже готово; задержка только во внешних шагах.</p>
{{else}}
<h3>Q: Что нужно, чтобы начать обмен с НРД?</h3>
<ol>
<li>Получить сертификат УЦ МБ для организации.</li>
<li>Оформить в НРД подключение к ЭДО и сервису MOEX МОСТ (M2M), получить доступ к нужному контуру.</li>
<li>Поднять Astra Linux ВМ, поставить ИШ + Валидату, импортировать сертификат.</li>
<li>В <a href="/admin/setup">Настройке</a> → «НРД» указать профиль, URL ИШ и ключевой контейнер, затем проверить подключения на странице <a href="/admin/status">«Статус»</a>.</li>
</ol>
{{end}}
</div>
{{if .DevMode}}
<div class="card">
<h2>Что у нас уже готово</h2>
<ul>
@@ -134,3 +145,4 @@
</ul>
</div>
{{end}}
{{end}}
@@ -18,7 +18,7 @@
<a href="#actions">Действия джобы по событиям</a>
<a href="#handbook">Справочник участников</a>
<a href="#runmode">Кто запускает обработку</a>
<a href="#replay">Выгрузка истории тестов</a>
{{if .DevMode}}<a href="#replay">Выгрузка истории тестов</a>{{end}}
<div class="toc-group">Практика</div>
<a href="#sql">Примеры SQL</a>
<a href="#questions">Открытые вопросы</a>
@@ -184,6 +184,7 @@ ORDER BY e.ID;</pre>
<p class="muted">Включать нужно один вариант, иначе процедура будет вызываться параллельно из двух мест. Просьба к разработчику: сделать <code>FTV_M2M_JOB</code> безопасной при повторном/параллельном вызове (обработка только строк со <code>STATE='TODO'</code>, блокировка строки на время обработки) — тогда случайное двойное включение не навредит.</p>
{{if .DevMode}}{{/* отчёт о выгрузке истории тестирования — только в режиме разработки */}}
<h2 id="replay">Результат выгрузки истории тестов (14.09.2026)</h2>
<p>Кнопкой «Выгрузить историю в DEPO» на вкладке «Тестирование» bj записал в трансферные таблицы все переводы, проведённые на TEST3 (робот НРД и банк Зенит): 78 переводов, 252 события с реальными датами. Джоба bj2depo создала по каждому документ в DEPO (D_CLISER_M2M + строки), сама определила контрагента, депонента, бумагу и место хранения. Что при этом выяснилось:</p>
<ul>
@@ -192,14 +193,15 @@ ORDER BY e.ID;</pre>
<li>После обработки событий статусы в DEPO не меняются: у всех документов <code>DOC_STATUS=0</code>, у строк <code>M2M_STATUS=0</code>, <code>DEP_STATUS=0</code>. <strong>Просьба согласовать отображение</strong>, например: <code>BJ_CP_REJECT</code> / <code>BJ_DECISION_REJECT</code> → строка «отказ» с кодом в M2M_TEXT; <code>BJ_CP_CONFIRM</code> / <code>BJ_DECISION_CONFIRM</code> → «принято»; <code>BJ_ORDER_ACCEPTED</code> / <code>BJ_EXECUTED</code> → документ «исполнен»; <code>BJ_SERVICE_ERROR</code> / <code>BJ_CANCELLED</code> → документ «отклонён / отменён». Тогда историю тестов можно будет просмотреть в DEPO с настоящими статусами.</li>
<li>Событие <code>BJ_PING</code> сейчас помечается ERROR — достаточно помечать SUCCESS без действий.</li>
</ul>
{{end}}
<h2 id="sql">Примеры SQL</h2>
<p>Так bj заведёт входящий перевод (одна транзакция):</p>
<pre class="copyable">BEGIN TRAN;
INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'IN', 'MC0034900000', N'Погонщикова Виктория Олеговна', '4500 123456');
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'IN', 'MC0034900000', N'Иванов Иван Иванович', '0000 000000');
INSERT INTO dbo.FTV_M2M_LINES (GUID, REFERENCE, ISIN, SEC_CODE, SHARE, AMOUNT, PLACE_NUMBER, DEPO_NUMBER)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'M2M0000002675550', 'RU000A107UL4', 'RU000A107UL4', 18824, 1, '36MC0413600000F00', N'ВТНКЦ00000816');
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', 'M2M0000002675550', 'RU000A107UL4', 'RU000A107UL4', 18824, 1, '36MC0413600000F00', N'ВТНКЦ00000001');
INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, EVENT_TYPE, EVENT_DATE)
VALUES ('5325B860-3265-49B6-986D-FA84FB41FE27', NULL, 'BJ_RECEIVED', '2026-09-17T09:21:26.000'); -- время получения M2MTR в bj
COMMIT;
@@ -12,7 +12,7 @@
<pre>curl -u "lk-user:lk-password" \
-H "Content-Type: application/json" \
http://10.10.10.22:8080/api/v1/back_office/claims/</pre>
<p class="muted">На M2 (дев-стенд) аутентификация отключена; включится на M3 — параллельно с подключением реального ЛК.</p>
{{if .DevMode}}<p class="muted">На M2 (дев-стенд) аутентификация отключена; включится на M3 — параллельно с подключением реального ЛК.</p>{{end}}
</div>
<div class="card">
@@ -75,6 +75,7 @@
}</pre>
</div>
{{if .DevMode}}
<div class="card">
<h2>7. Эмулятор ЛК для тестов</h2>
<p>На дев-стенде доступен <strong>lk-emulator</strong> на порту 8083 — имитация ЛК. Он сам регистрирует свой URL в bj-server как callback-приёмник.</p>
@@ -84,10 +85,11 @@
<li>При запуске реального ЛК эмулятор остаётся как QA-инструмент</li>
</ul>
</div>
{{end}}
<div class="card">
<h2>8. Подписание заявления</h2>
<h2>{{if .DevMode}}8{{else}}7{{end}}. Подписание заявления</h2>
<p>ЛК должен подписать заявление XMLDSig (ГОСТ или RSA) и положить в поле <code>signed_document</code> (base64). Мы проверяем подпись через crypto-service — см. <a href="/admin/help/crypto">инструкцию по криптографии</a>.</p>
<p class="muted">На M2 проверка подписи отключена (stub). На M3-M4 включится после подключения СКЗИ.</p>
{{if .DevMode}}<p class="muted">На M2 проверка подписи отключена (stub). На M3-M4 включится после подключения СКЗИ.</p>{{end}}
</div>
{{end}}
@@ -0,0 +1,87 @@
{{define "content"}}
<style>
.hm{max-width:880px}
.hm h2{margin-top:26px}
.hm ol li,.hm ul li{margin:5px 0}
.hm .tw table td:first-child{white-space:nowrap;font-weight:600}
</style>
<div class="hm">
<p style="margin:0 0 12px"><a href="/admin/help" class="btn">← Справка</a> <a href="/admin/manual" class="btn btn-secondary">К разделу</a></p>
<h1>Ручной ввод M2M: инструкция</h1>
<p>Раздел «Ручной ввод M2M» нужен, когда клиент подаёт поручение на перевод ценных бумаг <b>на бумаге</b> (в офисе, через представителя, почтой). Сотрудник депозитария вводит поручение в систему так, как клиент заполнил бы заявку в личном кабинете. После отправки перевод идёт тем же путём, что и заявка из личного кабинета, с той же автоматизацией.</p>
<p>Раздел работает только для <b>исходящих</b> переводов, когда бумаги списываются у нас. Входящие переводы приходят от контрагентов и обрабатываются в разделе «Журнал → Входящие».</p>
<h2>Кто работает в разделе</h2>
<ul>
<li>Просматривать поручения могут все пользователи с правом просмотра.</li>
<li>Вводить, сверять, отмечать и отправлять поручения может пользователь с правом «Операции».</li>
<li>Каждая отметка сохраняет, кто и когда её поставил. Эти данные попадают в лист сверки для печати.</li>
</ul>
<h2>Порядок работы</h2>
<ol>
<li><b>Примите поручение.</b> Проверьте, что поручение подписано клиентом или представителем, датировано и содержит реквизиты бумаг и принимающего депозитария. Если действует представитель, проверьте доверенность.</li>
<li><b>Создайте поручение в системе:</b> «Ручной ввод M2M» → «Новое поручение». Заполните поля по бумажному поручению (описание полей — ниже).</li>
<li><b>Найдите клиента в учётной системе</b> кнопкой «Найти в DEPO по документу», если учётная система подключена. Пустые поля (ФИО, дата рождения, счёт, наименования бумаг) заполнятся из учётной системы. Сверьте их с поручением.</li>
<li><b>Выполните сверку с DEPO</b> кнопкой «Сохранить и сверить с DEPO». Система проверяет:
<ul>
<li>клиента по документу, ФИО и дате рождения;</li>
<li>что счёт принадлежит клиенту, открыт и не является ИИС;</li>
<li>что выпуск российский, а бумага есть в справочнике и принята на обслуживание;</li>
<li>что остаток на торговом разделе достаточен и бумаги не заблокированы;</li>
<li>место хранения в НРД;</li>
<li>ограничения для квалифицированных инвесторов (как предупреждение).</li>
</ul>
Это те же проверки, что и для заявок из личного кабинета. Если сверка не пройдена, отправить поручение нельзя.</li>
<li><b>Проставьте обязательные отметки.</b> Каждая отметка подтверждает, что вы лично проверили пункт в учётной системе депозитария. Выберите, как заблокированы бумаги в торговой системе.</li>
<li><b>Отправьте поручение</b> кнопкой «Отправить в НРД». Кнопка доступна, только когда заполнены все реквизиты, сверка пройдена и все отметки проставлены. Над кнопкой показано, чего не хватает.</li>
<li><b>Следите за ходом исполнения</b> в карточке поручения (подробнее — ниже).</li>
<li><b>Закройте поручение отметками после ответа:</b> проведено ли списание в торговой и учётной системах, уведомлён ли клиент, есть ли отметка на бумажном поручении.</li>
<li><b>Распечатайте лист сверки</b> («Лист сверки для печати») и приложите его к бумажному поручению.</li>
</ol>
<h2>Поля поручения</h2>
<div class="tw"><table><tbody>
<tr><td>Номер и дата поручения</td><td>Реквизиты бумажного поручения клиента (входящий номер депозитария).</td></tr>
<tr><td>Как получено</td><td>Способ получения: лично, через представителя, почтой, курьером, через систему электронного документооборота.</td></tr>
<tr><td>Вид документа, серия, номер</td><td>Документ, удостоверяющий личность, по справочнику НРД. Для паспорта РФ (код 21) серия — 4 цифры, номер — 6 цифр. Принимающий депозитарий ищет клиента по этим данным, поэтому они должны точно совпадать с анкетой.</td></tr>
<tr><td>ФИО, дата рождения</td><td>Как в документе. Дата рождения сверяется с анкетой депонента.</td></tr>
<tr><td>Счёт депо клиента</td><td>Торговый счёт депо, с которого списываются бумаги. Он должен принадлежать клиенту и быть открыт.</td></tr>
<tr><td>Договор, код в торговой системе</td><td>Для сведения и поиска клиента в торговой системе.</td></tr>
<tr><td>Ценные бумаги</td><td>ISIN (только российские выпуски, начинаются с RU) и количество. Дробное количество допускается для паёв. Код НРД можно не указывать.</td></tr>
<tr><td>Принимающий депозитарий</td><td>Выбирается из справочника участников M2M, который bj получает от НРД. Если депозитария нет в справочнике, перевод через MOEX МОСТ невозможен.</td></tr>
<tr><td>Счёт у принимающего</td><td>Для сведения: принимающий депозитарий сам находит клиента по документу.</td></tr>
<tr><td>Место расчётов</td><td>НРД или СПБ Банк — как в поручении клиента.</td></tr>
<tr><td>Сведения о расходах</td><td>Отметьте, если клиент просит передать сведения о расходах на приобретение бумаг.</td></tr>
</tbody></table></div>
<p>Если после сверки или отметок изменить данные поручения, сверка и отметки сбросятся: их нужно пройти заново.</p>
<h2>Блокировка в торговой системе</h2>
<ul>
<li><b>Заблокировать автоматически при отправке.</b> Вариант доступен, если в настройках включён автоматический цикл в модуле неторговых поручений QUIK. bj регистрирует вывод бумаг, и лимит уменьшается. По исполнению bj подписывает списание, а при отказе снимает блокировку.</li>
<li><b>Заблокированы вручную.</b> Сотрудник сам регистрирует поручение на вывод в торговой системе и указывает его номер. После ответа сотрудник отмечает окончательное списание или снятие блокировки.</li>
<li><b>Не требуется.</b> Выберите этот вариант, если бумаги не учитываются в торговой системе.</li>
</ul>
<h2>Ход исполнения</h2>
<ol>
<li><b>Пакет отправлен в НРД.</b> Сервис MOEX МОСТ проверяет формат и присылает квитанцию. Если есть ошибка, поручение завершается отказом с кодом, а блокировка снимается.</li>
<li><b>Решение принимающего депозитария.</b> Депозитарий соглашается или отказывает (код отказа виден в карточке). Пакет принимается только целиком.</li>
<li><b>Черновик поручения 16.</b> После согласия НРД присылает черновик поручения на списание.</li>
<li><b>Поручение 16 подано.</b> Нажмите «Подать поручение 16 по черновику». Если включён автоматический режим M2M, bj подаёт поручение сам.</li>
<li><b>Отчёт НРД об исполнении.</b> Когда отчёт (MS010) получен, бумаги списаны. Кнопка «Документы НРД» скачивает архив с квитанциями и отчётом, в том числе в PDF.</li>
</ol>
<p>Сведения о переводе автоматически передаются в учётную систему депозитария, как для заявок из личного кабинета. Полная техническая информация (пакеты, ответы НРД) — в «Карточке перевода».</p>
<h2>Отклонение поручения</h2>
<p>Если поручение нельзя исполнить (например, нет подписи, недостаточно бумаг или депозитарий не участвует в M2M), нажмите «Отклонить поручение…» и укажите причину. В НРД ничего не отправляется. Уведомите клиента и сделайте отметку на бумажном поручении.</p>
<h2>Частые вопросы</h2>
<ul>
<li><b>Можно ли изменить поручение после отправки?</b> Нет. Если данные неверны, дождитесь отказа или исполнения и введите новое поручение.</li>
<li><b>Отправка не удалась.</b> Причина показана в карточке, поручение остаётся черновиком. Устраните причину (например, связь с НРД или торговой системой) и отправьте снова.</li>
<li><b>Нет кнопки «Найти в DEPO».</b> Учётная система не подключена к bj. Заполните поля вручную и выполните сверку в учётной системе сами: отметки подтверждают эту проверку.</li>
<li><b>Справочник участников пуст.</b> Администратор может запросить справочник в «Настройка → Диагностика», а пока ИНН можно ввести вручную.</li>
</ul>
</div>
{{end}}
@@ -127,7 +127,7 @@
<h2 id="verify">Как проверить, что всё готово</h2>
<ol>
<li>В bj: <a href="/admin/setup#sec-quik">Настройка → QUIK МНП</a> → «Проверить подключение». В отчёте должны быть видны процедуры <code>sp_NewInstrSec</code>, <code>sp_SignInstrSec</code>, <code>cancel_instruction</code> и таблица <code>M2MDepoRecipients</code>.</li>
<li>Скажите нам «проверяйте». Мы на согласованном тестовом клиенте регистрируем «Ввод ЦБ (M2M)» на 1 штуку (должно исполниться сразу, позиция +1), затем «Вывод ЦБ (M2M)» на 1 штуку с подписью депозитария (позиция −1). Весь цикл занимает две минуты, клиент видит оба поручения в терминале.</li>
<li>Скажите нам «проверяйте». Мы на согласованном клиенте для проверки регистрируем «Ввод ЦБ (M2M)» на 1 штуку (должно исполниться сразу, позиция +1), затем «Вывод ЦБ (M2M)» на 1 штуку с подписью депозитария (позиция −1). Весь цикл занимает две минуты, клиент видит оба поручения в терминале.</li>
<li>Признак готовности в самой базе МНП: у типов 100 и 101 снят признак Disabled, у классов INSTROUTM2M / INSTRINM2M есть фирма, инструменты привязаны к классам. Это мы проверяем сами.</li>
</ol>
@@ -149,7 +149,7 @@
<li>Точные коды инструментов, если они отличаются от <code>RUROUTM2M</code> / <code>RURINM2M</code> (шаг 3).</li>
<li>Что типы 100/101 включены и привязаны к классам (шаг 4), права техпользователю выданы (шаг 5).</li>
<li>Время перезапуска модуля (шаг 7).</li>
<li>Код тестового клиента QUIK, на котором разрешено проводить проверку, и что у него есть хотя бы одна бумага на позиции.</li>
<li>Код клиента QUIK, на котором разрешено проводить проверку, и что у него есть хотя бы одна бумага на позиции.</li>
</ul>
<h2 id="docs">Документы ARQA</h2>
@@ -7,7 +7,7 @@
</div>
<div class="card">
<h2>1. Интеграционный шлюз НРД (ИШ) и контуры тестирования</h2>
<h2>1. Интеграционный шлюз НРД (ИШ) и контуры НРД</h2>
<p>Основной канал отправки M2M-сообщений в НРД — Web-сервис <strong>ONYX</strong> через ИШ. ИШ сам подписывает пакеты ЭДО, поэтому в этом канале нам криптография не требуется. Дистрибутив ИШ скачивается с сайта НРД: <code>www.nsd.ru/workflow/system/programs/#0-widget-faq-0-4</code></p>
<p>Адреса контуров (из <code>DOC/Ссылки для доступа в тестовые контуры.pdf</code>):</p>
<table>
@@ -45,7 +45,7 @@
<li>URL ONYX — например <code>https://gost-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo</code></li>
<li>Ключевой контейнер — имя контейнера Валидаты с ключами ЭДО НРД (выдаются УЦ НРД, см. ниже)</li>
</ul>
<p class="muted">Без настроенного ИШ система работает в <strong>mock-режиме</strong>: bj-server эмитирует синтетический Decision через 3 секунды для каждой заявки. Это удобно для дев-демо и не требует подключения к НРД.</p>
{{if .DevMode}}<p class="muted">Без настроенного ИШ система работает в <strong>mock-режиме</strong>: bj-server эмитирует синтетический Decision через 3 секунды для каждой заявки. Это удобно для дев-демо и не требует подключения к НРД.</p>{{else}}<p class="muted">Пока ИШ не настроен, обмен с НРД не выполняется: поручения в НРД не отправляются.</p>{{end}}
</div>
<div class="card">
@@ -96,10 +96,10 @@
<table>
<thead><tr><th>Внешняя сторона</th><th>Что согласовать</th></tr></thead>
<tbody>
<tr><td>НРД (Национальный расчётный депозитарий)</td><td>Тестовые сертификаты GUEST/TEST3, дистрибутив ИШ, доступ к личному кабинету УЦ НРД</td></tr>
<tr><td>Команда ЛК (ESIA Finance)</td><td>Базовый URL ЛК, Basic-auth учётные данные, очерёдность подключения (сначала эмулятор, потом реальный ЛК)</td></tr>
<tr><td>НРД (Национальный расчётный депозитарий)</td><td>{{if .DevMode}}Тестовые сертификаты GUEST/TEST3{{else}}Сертификаты для тестового и боевого контуров{{end}}, дистрибутив ИШ, доступ к личному кабинету УЦ НРД</td></tr>
<tr><td>Команда ЛК (ESIA Finance)</td><td>Базовый URL ЛК, Basic-auth учётные данные, {{if .DevMode}}очерёдность подключения (сначала эмулятор, потом реальный ЛК){{else}}окружения ЛК (тест и бой){{end}}</td></tr>
<tr><td>Команда Fansy</td><td>Контракт <code>docs/fansy-contract/v1/</code>, SLA, окна обслуживания, IP-allowlist</td></tr>
<tr><td>Брокеры-контрагенты MOST</td><td>БКС (ИНН 5406121446), Ренессанс (7709258228), Альфа-Банк (7728168971) — уже в seed</td></tr>
<tr><td>Брокеры-контрагенты MOST</td><td>БКС (ИНН 5406121446), Ренессанс (7709258228), Альфа-Банк (7728168971){{if .DevMode}} — уже в seed{{end}}</td></tr>
</tbody>
</table>
</div>
@@ -13,8 +13,8 @@
{{end}}
</div>
{{if .LKUrl}}
{{/* ===== Первичный тест интеграции с ЛК ===== */}}
{{if and .DevMode .LKUrl}}
{{/* ===== Первичный тест интеграции с ЛК (режим разработки) ===== */}}
<div class="card" style="display:flex;gap:16px;align-items:center;flex-wrap:wrap">
<div style="width:42px;height:42px;border-radius:11px;background:var(--accent-weak);color:var(--accent);display:flex;align-items:center;justify-content:center;flex:none">
<svg viewBox="0 0 24 24" width="21" height="21" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
@@ -29,10 +29,11 @@
{{/* ===== Крупные плитки задач ===== */}}
<div class="tiles">
<a href="#" onclick="document.getElementById('sim-modal').style.display='flex';return false" class="tile brand">
{{/* В режиме разработки — симулятор перевода роботу НРД, у клиента — ручной ввод поручения */}}
<a {{if .DevMode}}href="#" onclick="document.getElementById('sim-modal').style.display='flex';return false"{{else}}href="/admin/manual"{{end}} class="tile brand">
<span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>
<span class="t-title">Новый перевод</span>
<span class="t-sub">Симулятор: сценарий робота + кол-во бумаг</span>
<span class="t-sub">{{if .DevMode}}Симулятор: сценарий робота + кол-во бумаг{{else}}Ручной ввод поручения клиента{{end}}</span>
<span class="t-arrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
</a>
<a href="/admin/claims" class="tile">
@@ -98,7 +99,7 @@
</tbody>
</table>
{{else}}
<p class="muted" style="margin:0">Переводов ещё нет. Нажмите «Новый перевод», чтобы создать первый.</p>
<p class="muted" style="margin:0">Переводов ещё нет. {{if .DevMode}}Нажмите «Новый перевод», чтобы создать первый.{{else}}Они появятся с заявками из личного кабинета, а поручение клиента можно ввести вручную — «Новый перевод».{{end}}</p>
{{end}}
</div>
@@ -120,7 +121,8 @@
</div>
{{end}}
{{/* ===== Модалка-симулятор: тестовый перевод роботу НРД ===== */}}
{{/* ===== Модалка-симулятор: тестовый перевод роботу НРД (режим разработки) ===== */}}
{{if .DevMode}}
<div id="sim-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;align-items:center;justify-content:center"
onclick="if(event.target===this)this.style.display='none'">
<div style="background:var(--card);border:1px solid var(--border);border-radius:14px;padding:24px;max-width:480px;width:92%;box-shadow:var(--shadow-lg)">
@@ -148,3 +150,4 @@
</div>
</div>
{{end}}
{{end}}
@@ -1,7 +1,7 @@
{{define "content"}}
<div class="hero">
<h1 class="hero-greeting">Входящие переводы</h1>
<span class="hero-status">Запросы на перевод, где контрагент (или робот в сценарии 3333) — передающая сторона. Нужно ответить: принять или отказать по каждой бумаге.</span>
<span class="hero-status">Запросы на перевод, где контрагент{{if .DevMode}} (или робот в сценарии 3333){{end}} — передающая сторона. Нужно ответить: принять или отказать по каждой бумаге.</span>
</div>
<div class="card">
@@ -16,14 +16,14 @@
<td><code>{{short 8 .GUID}}</code></td>
<td>{{$nm := m2mParty .SenderCode}}{{if not $nm}}{{$nm = m2mParty .SenderINN}}{{end}}{{if $nm}}<strong>{{$nm}}</strong><br>{{end}}<code>{{.SenderCode}}</code>{{if .SenderINN}} · ИНН {{.SenderINN}}{{end}}</td>
<td>{{len .Securities}}</td>
<td>{{$s := inStatus .}}<span class="badge {{$s.Class}}">{{$s.Text}}</span>{{if $s.Outcome}} <span class="badge {{if eq $s.Outcome "Принято"}}confirmed{{else if eq $s.Outcome "Отказано"}}rejected{{else}}awaiting_decision{{end}}">{{$s.Outcome}}</span>{{end}}{{if .DepoSummary}}<br><span class="badge {{if eq .DepoRecommend "confirm"}}confirmed{{else if eq .DepoRecommend "reject"}}rejected{{else}}awaiting_decision{{end}}" style="font-size:11px;margin-top:4px" title="Сверка с DEPO — эмуляция ответа, ничего не отправлено">сверка: {{.DepoSummary}}</span>{{end}}</td>
<td>{{$s := inStatus .}}<span class="badge {{$s.Class}}">{{$s.Text}}</span>{{if $s.Outcome}} <span class="badge {{if eq $s.Outcome "Принято"}}confirmed{{else if eq $s.Outcome "Отказано"}}rejected{{else}}awaiting_decision{{end}}">{{$s.Outcome}}</span>{{end}}{{if .DepoSummary}}<br><span class="badge {{if eq .DepoRecommend "confirm"}}confirmed{{else if eq .DepoRecommend "reject"}}rejected{{else}}awaiting_decision{{end}}" style="font-size:11px;margin-top:4px" title="Сверка с DEPO — {{if $.DevMode}}эмуляция ответа{{else}}рекомендация{{end}}, ничего не отправлено">сверка: {{.DepoSummary}}</span>{{end}}</td>
<td><a href="/admin/incoming/{{.ID}}">{{if eq .Status "answered"}}просмотр{{else}}ответить{{end}} →</a></td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="muted">Входящих переводов нет. Они появляются, когда другая сторона инициирует перевод к нам (напр. робот НРД в сценарии 3333 «Выступить принимающей стороной»).</p>
<p class="muted">Входящих переводов нет. Они появляются, когда другая сторона инициирует перевод к нам{{if .DevMode}} (напр. робот НРД в сценарии 3333 «Выступить принимающей стороной»){{end}}.</p>
{{end}}
<p class="muted" style="font-size:12px;margin-top:10px">🔄 Страница обновляется автоматически при появлении нового входящего.</p>
</div>
@@ -44,7 +44,7 @@
.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
</style>
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<h2 style="margin:0">Сверка с DEPO — эмуляция ответа</h2>
<h2 style="margin:0">Сверка с DEPO — {{if .DevMode}}эмуляция ответа{{else}}рекомендация по ответу{{end}}</h2>
{{if .Depo.Ran}}<span class="dc-rec {{.Depo.Recommend}}">рекомендуем: {{.Depo.Summary}}</span>{{end}}
</div>
<p class="muted" style="font-size:12.5px;margin:6px 0 0">Проверки по регламенту депозитария (инвестор, торговый счёт депо, бумага на обслуживании, договор не ИИС, блокировки). <b>Ответ контрагенту НЕ отправлен</b> — это только рекомендация; она подставлена в форму ниже, проверьте и нажмите «Отправить решение».</p>
@@ -73,7 +73,7 @@
<div class="card">
<h2>Решение по бумагам ({{len .It.Securities}})</h2>
{{if $answered}}
<p class="muted">Решение уже отправлено роботу. Ниже — что было выбрано по каждой бумаге.</p>
<p class="muted">Решение уже отправлено {{if .DevMode}}роботу{{else}}контрагенту через НРД{{end}}. Ниже — что было выбрано по каждой бумаге.</p>
<table>
<thead><tr><th>ReferenceId</th><th>Код НРД / ISIN</th><th>Кол-во</th><th>Счёт зачисления</th><th>Решение</th></tr></thead>
<tbody>
@@ -89,8 +89,9 @@
</tbody>
</table>
{{else}}
<p class="muted">По каждой бумаге выберите: <b>принять</b> (зачислим на наш депо-счёт из настроек НРД) или <b>отказать</b> (с кодом отказа). Решение уйдёт роботу одним сообщением M2MTransferDecision.</p>
<p class="muted">По каждой бумаге выберите: <b>принять</b> (зачислим на наш депо-счёт из настроек НРД) или <b>отказать</b> (с кодом отказа). Решение уйдёт {{if .DevMode}}роботу{{else}}контрагенту через НРД{{end}} одним сообщением M2MTransferDecision.</p>
{{if .DevMode}}{{/* подсказки по сценариям робота НРД — только в режиме разработки */}}
<div style="padding:10px 14px;background:var(--accent-weak,#e7f0fb);border-radius:10px;margin:6px 0 14px">
<label style="font-size:13px;font-weight:600">Сценарий тестирования:
<select id="m2m-scen" style="margin-left:8px;padding:5px 8px;border-radius:7px;border:1px solid var(--border);background:var(--card);color:var(--text)">
@@ -113,6 +114,7 @@
});
})();
</script>
{{end}}
<form method="post" action="/admin/incoming/{{.It.ID}}/answer">
<table>
@@ -140,7 +142,7 @@
</table>
<p class="muted" style="font-size:12.5px;margin:10px 0 0">Пакет принимается только целиком: при отказе по одной бумаге отказ ставится всем бумагам с тем же кодом (так и будет отправлено).</p>
<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap">
<button type="submit" class="btn btn-ok">Отправить решение роботу</button>
<button type="submit" class="btn btn-ok">Отправить решение{{if .DevMode}} роботу{{end}}</button>
<button type="button" class="btn" onclick="m2mSetAll('confirm')">Принять все</button>
<button type="button" class="btn" onclick="m2mSetAll('reject')">Отказать все</button>
</div>
@@ -354,17 +356,19 @@
{{/* ===== QUIK МНП: зачисление клиенту ===== */}}
{{$back := printf "/admin/incoming/%s" .It.ID}}
{{if .DevMode}}{{/* прогон копии в DEPO — служебный инструмент режима разработки */}}
<div class="card">
<h2 style="margin:0">Тестовый прогон в DEPO с реальным клиентом</h2>
<p class="muted" style="font-size:12.5px;margin:6px 0 8px">Копия этого входящего (те же бумаги и решения) записывается в трансферные таблицы DEPO под новым GUID на реального клиента из картотеки — по паспорту. Джоба bj2depo создаёт по нему документ; в НРД ничего не уходит. Полный сценарий приёма: получен → приняли → подали 16/1 → НРД принял → исполнено.</p>
<form method="post" action="/admin/incoming/depo-sim" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center" onsubmit="return confirm('Записать копию перевода в трансферные таблицы DEPO на клиента с этим паспортом?')">
<input type="hidden" name="id" value="{{.It.ID}}">
<label style="margin:0;font-size:12.5px">серия <input type="text" name="ser" value="1110" style="width:70px"></label>
<label style="margin:0;font-size:12.5px">номер <input type="text" name="num" value="754503" style="width:90px"></label>
<label style="margin:0;font-size:12.5px">серия <input type="text" name="ser" value="" placeholder="0000" required style="width:70px"></label>
<label style="margin:0;font-size:12.5px">номер <input type="text" name="num" value="" placeholder="000000" required style="width:90px"></label>
<button type="submit" class="btn">🗂 Прогнать в DEPO с реальным клиентом</button>
<span class="muted" style="font-size:12px">по умолчанию — Погонщикова В.О. (ВТНКЦ00000816 / BR859)</span>
<span class="muted" style="font-size:12px">паспорт клиента из картотеки DEPO</span>
</form>
</div>
{{end}}
<div class="card">
<style>
.qk-steps{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0}
@@ -0,0 +1,354 @@
{{define "content"}}
<style>
.mf-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px 14px}
.mf-grid label{display:block;font-size:12px;color:var(--muted);margin-bottom:3px}
.mf-grid input,.mf-grid select,.mf-grid textarea{width:100%;padding:7px 9px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);font-size:13.5px;box-sizing:border-box}
.mf-grid .wide{grid-column:1/-1}
.mf-h{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:18px 0 8px}
.mf-sec{display:grid;grid-template-columns:1.2fr .9fr 2fr .7fr auto;gap:8px;align-items:center;margin-bottom:6px}
.mf-sec input{width:100%;padding:7px 9px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);font-size:13.5px;box-sizing:border-box}
.mf-sec .x{border:none;background:none;color:var(--err);font-size:18px;cursor:pointer}
@media (max-width:720px){.mf-sec{grid-template-columns:1fr 1fr}.mf-sec .nm{grid-column:1/-1}}
.mf-req::after{content:" *";color:var(--err)}
.mf-note{font-size:12.5px;color:var(--muted)}
.mf-block{list-style:none;margin:8px 0 0;padding:0}
.mf-block li{padding:4px 0 4px 22px;position:relative;font-size:13.5px}
.mf-block li::before{content:"✕";position:absolute;left:2px;color:var(--err);font-weight:700}
.mf-chk{display:flex;gap:10px;align-items:flex-start;padding:9px 0;border-bottom:1px solid var(--border)}
.mf-chk input{width:18px;height:18px;margin-top:2px;flex:none}
.mf-chk .t{font-weight:600;font-size:13.5px}
.mf-chk .h{font-size:12.5px;color:var(--muted);margin-top:2px}
.mf-chk .who{font-size:12px;color:var(--ok);margin-top:2px}
.mf-grp{font-size:12px;font-weight:700;color:var(--accent);margin:14px 0 0;text-transform:uppercase;letter-spacing:.04em}
.mf-steps{list-style:none;margin:0;padding:0}
.mf-steps li{display:flex;gap:12px;padding:9px 0;border-bottom:1px solid var(--border)}
.mf-steps .d{width:24px;height:24px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;background:var(--card-2);color:var(--muted);border:1px solid var(--border)}
.mf-steps .done .d{background:var(--ok-weak);color:var(--ok);border-color:var(--ok)}
.mf-steps .wait .d{background:var(--warn-weak);color:var(--warn);border-color:var(--warn)}
.mf-steps .fail .d{background:var(--err-weak);color:var(--err);border-color:var(--err)}
.mf-steps .t{font-weight:600;font-size:13.5px}
.mf-steps .s{font-size:12.5px;color:var(--muted)}
.mf-kv{display:grid;grid-template-columns:minmax(150px,220px) 1fr;gap:4px 14px;font-size:13.5px}
.mf-kv .k{color:var(--muted)}
.dc-ok{color:var(--ok);font-weight:700}.dc-bad{color:var(--err);font-weight:700}.dc-warn{color:var(--warn);font-weight:700}
.mf-sign{display:grid;grid-template-columns:1fr 1fr;gap:40px;margin-top:40px}
.mf-sign div{border-top:1px solid #000;padding-top:4px;font-size:12px}
@media print{
header,nav,.topbar,.banner-mock,.no-print{display:none!important}
body{background:#fff!important;color:#000!important}
.card{box-shadow:none!important;border:1px solid #999!important;break-inside:avoid}
main{padding:0!important;max-width:none!important}
}
</style>
{{if .Flash}}<div class="card no-print" style="border-color:var(--accent)">{{.Flash}}</div>{{end}}
{{/* ======================= СПИСОК ======================= */}}
{{if eq .Mode "list"}}
<div class="section-head" style="align-items:center">
<h1 style="margin:0">Ручной ввод M2M</h1>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<a href="/admin/help/manual" class="btn btn-secondary">Инструкция</a>
{{if can "operate"}}<a href="/admin/manual/new" class="btn btn-ok">+ Новое поручение</a>{{end}}
</div>
</div>
<p class="muted" style="max-width:80ch">Исходящий перевод ценных бумаг через MOEX МОСТ по <b>бумажному поручению клиента</b>. Сотрудник вводит поручение так, как клиент заполнил бы заявку в личном кабинете, сверяет его с учётной и торговой системами, проставляет обязательные отметки и отправляет. Дальше перевод идёт тем же путём, что и заявка из личного кабинета: ответ контрагента, поручение 16, отчёт НРД об исполнении.</p>
<div class="card">
{{if .List}}
<div class="tw"><table>
<thead><tr><th>№</th><th>Поручение клиента</th><th>Клиент</th><th>Бумаги</th><th>Принимающий депозитарий</th><th>Состояние</th><th></th></tr></thead>
<tbody>
{{range .List}}
<tr>
<td><code>{{.O.Number}}</code></td>
<td class="num">№ {{.O.PaperNo}} от {{.O.PaperDate}}</td>
<td>{{.O.FullName}}</td>
<td class="num">{{len .O.Securities}}</td>
<td>{{if .O.RecvName}}{{.O.RecvName}}{{else}}ИНН {{.O.RecvINN}}{{end}}</td>
<td><span class="badge {{if eq .State "done"}}done{{else if or (eq .State "rejected") (eq .State "declined")}}rejected{{else if eq .State "sent"}}awaiting_decision{{else}}draft{{end}}">{{.StateRu}}</span></td>
<td><a href="/admin/manual/{{.O.ID}}">открыть →</a></td>
</tr>
{{end}}
</tbody>
</table></div>
{{else}}
<p class="muted" style="margin:0">Поручений ещё нет. Нажмите «Новое поручение», когда клиент принесёт поручение на перевод.</p>
{{end}}
</div>
{{end}}
{{if eq .Mode "edit"}}
<p class="no-print" style="margin:0 0 12px"><a href="/admin/manual" class="btn">← К списку</a></p>
<div class="card">
<h2>Новое поручение на перевод M2M</h2>
<p class="muted">Заполните поля по бумажному поручению клиента. После сохранения выполните сверку с учётной системой и проставьте обязательные отметки — только после этого поручение можно отправить.</p>
{{template "manualForm" .}}
</div>
{{end}}
{{/* ======================= КАРТОЧКА ======================= */}}
{{if or (eq .Mode "card") (eq .Mode "print")}}
{{$o := .O}}
<div class="no-print" style="display:flex;gap:8px;flex-wrap:wrap;margin:0 0 12px">
<a href="/admin/manual" class="btn">← К списку</a>
<a href="/admin/manual/{{$o.ID}}/print" class="btn btn-secondary" target="_blank">Лист сверки для печати</a>
{{if $o.DealID}}<a href="/admin/claims/{{$o.DealID}}" class="btn btn-secondary">Карточка перевода</a>{{end}}
</div>
<div class="card">
<div style="display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center">
<h2 style="margin:0">{{if eq .Mode "print"}}Лист сверки поручения на перевод ценных бумаг (MOEX МОСТ){{else}}Поручение {{$o.Number}}{{end}}</h2>
<span class="badge {{if eq .Progress.Outcome "done"}}done{{else if eq .Progress.Outcome "rejected"}}rejected{{else if eq $o.Status "sent"}}awaiting_decision{{else}}draft{{end}}">{{if eq .Progress.Outcome "done"}}исполнено{{else if eq .Progress.Outcome "rejected"}}{{if eq $o.Status "declined"}}отклонено депозитарием{{else}}отказ{{end}}{{else if eq $o.Status "sent"}}в работе{{else}}черновик{{end}}</span>
</div>
<div class="mf-kv" style="margin-top:12px">
<div class="k">Номер в системе</div><div>{{$o.Number}} · введено {{msk $o.CreatedAt}}{{if $o.CreatedBy}} · {{$o.CreatedBy}}{{end}}</div>
<div class="k">Поручение клиента</div><div>№ {{$o.PaperNo}} от {{$o.PaperDate}} · {{$o.ReceivedVia}}</div>
<div class="k">Клиент</div><div><b>{{$o.FullName}}</b>, дата рождения {{$o.BirthDate}}</div>
<div class="k">Документ</div><div>код {{$o.DocType}} · {{$o.DocSeries}} № {{$o.DocNumber}}{{if $o.DocIssueDate}} · выдан {{$o.DocIssueDate}}{{end}}{{if $o.DocIssuer}} · {{$o.DocIssuer}}{{end}}</div>
<div class="k">Счёт депо / договор</div><div>{{$o.Account}}{{if $o.Contract}} · договор {{$o.Contract}}{{end}}{{if $o.QuikCode}} · код в торговой системе {{$o.QuikCode}}{{end}}</div>
<div class="k">Принимающий депозитарий</div><div>{{$o.RecvName}} · ИНН {{$o.RecvINN}}{{if $o.RecvCode}} · {{$o.RecvCode}}{{end}}{{if $o.RecvAccount}} · счёт клиента {{$o.RecvAccount}}{{end}}</div>
<div class="k">Место расчётов</div><div>{{if eq $o.SettlePlace "spb"}}СПБ Банк{{else}}НРД{{end}}{{if $o.CostInfo}} · со сведениями о расходах{{end}}</div>
<div class="k">Бумаги</div><div>{{range $o.Securities}}<div>{{.ISIN}}{{if .Code}} ({{.Code}}){{end}}{{if .Name}} — {{.Name}}{{end}} × <b>{{.Qty}}</b></div>{{end}}</div>
{{if $o.Comment}}<div class="k">Комментарий</div><div>{{$o.Comment}}</div>{{end}}
{{if $o.GUID}}<div class="k">Идентификатор перевода</div><div><code>{{$o.GUID}}</code>, отправлено {{msk $o.SentAt}} · {{$o.SentBy}}</div>{{end}}
</div>
</div>
{{if and (eq $o.Status "draft") (eq .Mode "card")}}
{{if $o.SendErr}}<div class="card" style="border-color:var(--err)"><b style="color:var(--err)">Последняя попытка отправки не удалась:</b> {{$o.SendErr}}</div>{{end}}
<div class="card no-print">
<h2>Реквизиты</h2>
{{if .Problems}}<p class="muted" style="margin:0">Нужно исправить:</p><ul class="mf-block">{{range .Problems}}<li>{{.}}</li>{{end}}</ul>{{end}}
<details {{if .Problems}}open{{end}} style="margin-top:10px"><summary style="cursor:pointer;color:var(--accent)">Изменить данные поручения</summary>
<div style="margin-top:12px">{{template "manualForm" .}}</div>
</details>
</div>
{{end}}
{{/* ---- Сверка с учётной системой ---- */}}
{{if or $o.Depo (and (eq $o.Status "draft") .DepoOn)}}
<div class="card" id="depo">
<div style="display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center">
<h2 style="margin:0">Сверка с учётной системой (DEPO)</h2>
{{if and (eq $o.Status "draft") .DepoOn (eq $.Mode "card")}}
<form method="post" action="/admin/manual/save" class="no-print" style="margin:0" id="depoRerun">
<input type="hidden" name="id" value="{{$o.ID}}"><input type="hidden" name="do" value="depo">
<button type="button" class="btn" onclick="mfRerun()">{{if $o.Depo}}Сверить заново{{else}}Сверить с DEPO{{end}}</button>
</form>{{end}}
</div>
{{with $o.Depo}}
<p style="margin:10px 0 6px">{{msk .At}}{{if .By}} · {{.By}}{{end}} ·
{{if not .Ran}}<span class="dc-bad">не выполнена: {{.Err}}</span>{{else if eq .Recommend "confirm"}}<span class="dc-ok">{{.Summary}}</span>{{else}}<span class="dc-bad">{{.Summary}}</span>{{end}}</p>
{{if $.DepoStale}}<p class="dc-warn">Данные поручения изменены после сверки — выполните сверку заново.</p>{{end}}
{{if .Ran}}<p class="muted" style="margin:0 0 8px">Инвестор в DEPO: {{if .Investor}}{{.Investor}}{{else}}не найден{{end}} · счёт: {{if .Account}}{{.Account}}{{else}}—{{end}}</p>{{end}}
{{range .Secs}}
<div class="mf-grp">{{.Security}} — {{if eq .Recommend "confirm"}}<span class="dc-ok">можно списать</span>{{else}}<span class="dc-bad">нельзя{{if .RejectCode}} ({{.RejectCode}}){{end}}</span>{{end}}</div>
<div class="tw"><table style="margin-top:4px"><tbody>
{{range .Checks}}<tr><td style="width:24px">{{if not .OK}}<span class="dc-bad">✕</span>{{else if .Warn}}<span class="dc-warn">!</span>{{else}}<span class="dc-ok">✓</span>{{end}}</td><td style="width:38%">{{.Name}}</td><td class="muted">{{.Detail}}</td></tr>{{end}}
</tbody></table></div>
{{end}}
{{else}}
<p class="muted" style="margin:10px 0 0">Сверка ещё не выполнялась. Она проверяет клиента, счёт, остаток, блокировки, ограничения по бумагам — те же проверки, что и для заявок из личного кабинета.</p>
{{end}}
</div>
{{end}}
{{if and (eq $o.Status "draft") (not .DepoOn)}}
<div class="card"><h2>Сверка с учётной системой</h2><p class="muted" style="margin:0">Учётная система депозитария не подключена к bj — сверку выполните вручную и подтвердите отметками ниже.</p></div>
{{end}}
{{/* ---- Обязательные отметки ---- */}}
{{if or (eq $o.Status "draft") $o.Checks}}
<div class="card" id="checks">
<h2>Обязательные отметки перед отправкой</h2>
{{$editable := and (eq $o.Status "draft") (eq .Mode "card") (can "operate")}}
<form method="post" action="/admin/manual/{{$o.ID}}/checks" id="chkForm">
{{$grp := ""}}
{{range .PreChecks}}
{{if ne .Group $grp}}<div class="mf-grp">{{.Group}}</div>{{$grp = .Group}}{{end}}
{{$m := index $o.Checks .Key}}
<label class="mf-chk"><input type="checkbox" name="check_{{.Key}}" value="1" {{if $m.Done}}checked{{end}} {{if not $editable}}disabled{{end}} onchange="this.form.submit()">
<span><div class="t">{{.Title}}</div><div class="h">{{.Hint}}</div>{{if $m.Done}}<div class="who">✓ {{$m.By}} · {{msk $m.At}}</div>{{end}}</span></label>
{{end}}
<div class="mf-grp">Торговая система (QUIK)</div>
<label class="mf-chk"><input type="radio" name="quik_mode" value="auto" {{if eq $o.QuikMode "auto"}}checked{{end}} {{if or (not $editable) (not .QuikAuto)}}disabled{{end}} onchange="this.form.submit()">
<span><div class="t">Заблокировать бумаги автоматически при отправке</div><div class="h">{{if .QuikAuto}}bj зарегистрирует в модуле неторговых поручений вывод бумаг (лимит уменьшится), по исполнению подпишет списание, по отказу снимет блокировку.{{else}}Недоступно: автоматический цикл в торговой системе не включён в настройках.{{end}}</div></span></label>
<label class="mf-chk"><input type="radio" name="quik_mode" value="manual" {{if eq $o.QuikMode "manual"}}checked{{end}} {{if not $editable}}disabled{{end}} onchange="this.form.submit()">
<span><div class="t">Бумаги заблокированы вручную (лимит уменьшен)</div><div class="h">Позиция клиента проверена, поручение на вывод зарегистрировано сотрудником. После ответа отметьте списание или снятие блокировки.</div>
{{if eq $o.QuikMode "manual"}}<div style="margin-top:6px;display:flex;gap:8px;align-items:center;flex-wrap:wrap"><span class="mf-note">№ поручения в торговой системе:</span><input name="quik_instr" value="{{$o.QuikInstr}}" {{if not $editable}}disabled{{end}} style="max-width:200px;padding:5px 8px;border-radius:7px;border:1px solid var(--border);background:var(--card);color:var(--text)">{{if $editable}}<button type="submit" class="btn btn-secondary" style="padding:5px 10px">Сохранить</button>{{end}}</div>{{end}}
</span></label>
<label class="mf-chk"><input type="radio" name="quik_mode" value="none" {{if eq $o.QuikMode "none"}}checked{{end}} {{if not $editable}}disabled{{end}} onchange="this.form.submit()">
<span><div class="t">Блокировка в торговой системе не требуется</div><div class="h">Бумаги не учитываются в торговой системе (не на торговом разделе, клиент не пользуется брокерским обслуживанием).</div></span></label>
{{if and $editable (ne $o.QuikMode "manual")}}<input type="hidden" name="quik_instr" value="{{$o.QuikInstr}}">{{end}}
</form>
</div>
{{end}}
{{/* ---- Отправка ---- */}}
{{if and (eq $o.Status "draft") (eq .Mode "card")}}
<div class="card no-print" id="send">
<h2>Отправка в НРД</h2>
{{if .Blockers}}
<p class="muted" style="margin:0">Отправить пока нельзя:</p>
<ul class="mf-block">{{range .Blockers}}<li>{{.}}</li>{{end}}</ul>
{{else}}
<p style="margin:0"><span class="dc-ok">Все проверки и отметки пройдены.</span> Поручение уйдёт в НРД тем же путём, что и заявка из личного кабинета{{if eq $o.QuikMode "auto"}}, бумаги будут заблокированы в торговой системе перед отправкой{{end}}.</p>
{{end}}
{{if can "operate"}}
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;align-items:flex-start">
<form method="post" action="/admin/manual/{{$o.ID}}/send" onsubmit="return confirm('Отправить поручение {{$o.Number}} ({{$o.FullName}}) в НРД? Отменить отправку будет нельзя.')">
<button type="submit" class="btn btn-ok" {{if .Blockers}}disabled style="opacity:.5;cursor:not-allowed"{{end}}>Отправить в НРД</button>
</form>
<details><summary class="btn btn-secondary" style="list-style:none;cursor:pointer">Отклонить поручение…</summary>
<form method="post" action="/admin/manual/{{$o.ID}}/decline" style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap" onsubmit="return confirm('Отклонить поручение без отправки в НРД?')">
<input name="reason" required placeholder="причина отказа клиенту" style="min-width:320px;padding:7px 9px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text)">
<button type="submit" class="btn">Отклонить</button>
</form>
</details>
</div>
{{end}}
</div>
{{end}}
{{/* ---- Ход исполнения ---- */}}
{{if ne $o.Status "draft"}}
<div class="card" id="progress">
<h2>Ход исполнения</h2>
<ul class="mf-steps">
{{range $i, $s := .Progress.Steps}}
<li class="{{$s.State}}"><span class="d">{{if eq $s.State "done"}}✓{{else if eq $s.State "fail"}}✕{{else}}{{add $i 1}}{{end}}</span>
<div><div class="t">{{$s.Title}}</div><div class="s">{{if not $s.At.IsZero}}{{msk $s.At}}{{end}}{{if and (not $s.At.IsZero) $s.Detail}} · {{end}}{{$s.Detail}}</div></div></li>
{{end}}
</ul>
{{with .Progress.Depo}}
<div class="no-print" style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
{{if and (not .Submitted) (can "operate")}}
<form method="post" action="/admin/outgoing/{{.GUID}}/submit-order" style="margin:0" onsubmit="return confirm('Подать поручение 16 (списание) в НРД по полученному черновику?')">
<input type="hidden" name="back" value="/admin/manual/{{$o.ID}}">
<button type="submit" class="btn btn-ok">Подать поручение 16 по черновику</button>
</form>
{{if $.Auto}}<span class="mf-note" style="align-self:center">Автоматический режим включён — bj подаст поручение сам в течение минуты.</span>{{end}}
{{end}}
<a href="/admin/outgoing/{{.GUID}}/draft.zip" class="btn btn-secondary">Черновик 16 (ZIP)</a>
{{if .Submitted}}<a href="/admin/outgoing/{{.GUID}}/order.xml" class="btn btn-secondary">Поданное поручение 16</a>{{end}}
{{if .Done}}<a href="/admin/outgoing/{{.GUID}}/ms010.zip" class="btn btn-ok">Документы НРД (ZIP + PDF)</a>{{end}}
</div>
{{end}}
{{if .Progress.Quik}}
<div class="mf-grp">Торговая система</div>
<div class="tw"><table style="margin-top:4px"><tbody>
{{range .Progress.Quik}}<tr><td>{{.ISIN}} × {{.Qty}}</td><td>{{.StateRu}}</td><td class="muted">{{if .InstrNo}}поручение № {{.InstrNo}}{{end}}{{if .Err}} · {{.Err}}{{end}}</td></tr>{{end}}
</tbody></table></div>
{{else if eq $o.QuikMode "manual"}}<p class="muted" style="margin:10px 0 0">Торговая система: бумаги заблокированы вручную, поручение № {{$o.QuikInstr}}.</p>
{{else if eq $o.QuikMode "none"}}<p class="muted" style="margin:10px 0 0">Торговая система: блокировка не требовалась.</p>{{end}}
</div>
{{/* ---- Отметки после ответа ---- */}}
{{if .Progress.Outcome}}
<div class="card" id="post">
<h2>{{if eq .Progress.Outcome "done"}}После исполнения{{else}}После отказа{{end}}</h2>
<form method="post" action="/admin/manual/{{$o.ID}}/post">
{{range .PostChecks}}{{if and (eq .When $.Progress.Outcome) (or (not .Quik) (eq $o.QuikMode "manual"))}}
{{$m := index $o.Post .Key}}
<input type="hidden" name="shown_{{.Key}}" value="1">
<label class="mf-chk"><input type="checkbox" name="post_{{.Key}}" value="1" {{if $m.Done}}checked{{end}} {{if or (ne $.Mode "card") (not (can "operate"))}}disabled{{end}} onchange="this.form.submit()">
<span><div class="t">{{.Title}}</div>{{if $m.Done}}<div class="who">✓ {{$m.By}} · {{msk $m.At}}</div>{{end}}</span></label>
{{end}}{{end}}
</form>
{{if and (eq .Progress.Outcome "rejected") (eq $o.QuikMode "auto")}}<p class="mf-note">Блокировку в торговой системе bj снимает автоматически — состояние видно выше.</p>{{end}}
</div>
{{end}}
{{end}}
{{if eq .Mode "print"}}
<div class="mf-sign">
<div>Сотрудник депозитария (ФИО, подпись)</div>
<div>Контролёр (ФИО, подпись)</div>
</div>
<p class="mf-note" style="margin-top:20px">Сформировано {{.Now}}.</p>
<script>window.addEventListener('load',()=>setTimeout(()=>window.print(),300))</script>
{{end}}
<script>
function mfRerun(){const f=document.getElementById('mf');if(f&&f.closest('details')&&f.closest('details').open){const b=document.createElement('input');b.type='hidden';b.name='do';b.value='depo';f.appendChild(b);f.submit()}else{document.getElementById('depoRerun').submit()}}
</script>
{{end}}
{{end}}
{{/* ======================= ФОРМА (новое / черновик) ======================= */}}
{{define "manualForm"}}
<form method="post" action="/admin/manual/save" id="mf">
<input type="hidden" name="id" value="{{.O.ID}}">
<div class="mf-h" style="margin-top:0">1. Основание</div>
<div class="mf-grid">
<div><label class="mf-req">Номер поручения клиента</label><input name="paper_no" value="{{.O.PaperNo}}" required></div>
<div><label class="mf-req">Дата поручения</label><input type="date" name="paper_date" value="{{.O.PaperDate}}" required></div>
<div><label>Как получено</label>
<select name="received_via">
{{$v := .O.ReceivedVia}}
{{range $opt := (list "лично в офисе" "через представителя по доверенности" "почтой" "курьером" "через систему электронного документооборота")}}<option {{if eq $v $opt}}selected{{end}}>{{$opt}}</option>{{end}}
</select></div>
</div>
<div class="mf-h">2. Клиент (как в поручении и анкете депонента)</div>
<div class="mf-grid">
<div><label class="mf-req">Вид документа</label>
<select name="doc_type">{{$dt := .O.DocType}}{{range .DocTypes}}<option value="{{.Code}}" {{if eq $dt .Code}}selected{{end}}>{{.Code}} — {{.Title}}</option>{{end}}</select></div>
<div><label>Серия</label><input name="doc_series" value="{{.O.DocSeries}}" placeholder="4 цифры для паспорта РФ"></div>
<div><label class="mf-req">Номер</label><input name="doc_number" value="{{.O.DocNumber}}"></div>
<div style="display:flex;align-items:flex-end">{{if .DepoOn}}<button type="submit" name="do" value="lookup" class="btn btn-secondary" style="width:100%" title="Найти клиента в учётной системе по документу и заполнить пустые поля">Найти в DEPO по документу</button>{{end}}</div>
<div><label>Дата выдачи</label><input type="date" name="doc_issue_date" value="{{.O.DocIssueDate}}"></div>
<div class="wide"><label>Кем выдан</label><input name="doc_issuer" value="{{.O.DocIssuer}}"></div>
<div><label class="mf-req">Фамилия</label><input name="last_name" value="{{.O.LastName}}"></div>
<div><label class="mf-req">Имя</label><input name="first_name" value="{{.O.FirstName}}"></div>
<div><label>Отчество</label><input name="middle_name" value="{{.O.MiddleName}}"></div>
<div><label class="mf-req">Дата рождения</label><input type="date" name="birth_date" value="{{.O.BirthDate}}"></div>
<div><label>Договор (брокерский / депозитарный)</label><input name="contract" value="{{.O.Contract}}"></div>
<div><label class="mf-req">Счёт депо клиента</label><input name="account" value="{{.O.Account}}" placeholder="торговый счёт депо"></div>
<div><label>Код клиента в торговой системе</label><input name="quik_code" value="{{.O.QuikCode}}"></div>
<div><label>Телефон для связи</label><input name="phone" value="{{.O.Phone}}"></div>
</div>
<div class="mf-h">3. Ценные бумаги</div>
<div class="mf-sec mf-note" style="margin-bottom:2px"><span class="mf-req">ISIN</span><span>Код НРД</span><span class="nm">Наименование</span><span class="mf-req">Количество</span><span></span></div>
<div id="secs">
{{range .O.Securities}}
<div class="mf-sec"><input name="sec_isin" value="{{.ISIN}}" placeholder="RU…" maxlength="12"><input name="sec_code" value="{{.Code}}" placeholder="необязательно"><input class="nm" name="sec_name" value="{{.Name}}" placeholder="заполнится из DEPO"><input name="sec_qty" value="{{.Qty}}" inputmode="decimal"><button type="button" class="x" onclick="mfDel(this)" title="Убрать">×</button></div>
{{end}}
</div>
<button type="button" class="btn btn-secondary" onclick="mfAdd()" style="margin-top:4px">+ Бумага</button>
<p class="mf-note">Через MOEX МОСТ переводятся только российские выпуски (ISIN начинается с RU). Дробное количество — для паёв, через точку или запятую.</p>
<div class="mf-h">4. Принимающая сторона</div>
<div class="mf-grid">
<div class="wide"><label>Депозитарий из справочника участников M2M</label>
<select id="hb" onchange="mfPickHB(this)">
<option value="">— выбрать —</option>
{{$inn := .O.RecvINN}}{{range .Handbook}}<option value="{{.INN}}" data-name="{{.Name}}" {{if eq $inn .INN}}selected{{end}}>{{.Name}} · ИНН {{.INN}} · {{.Code}}</option>{{end}}
</select>
{{if not .Handbook}}<span class="mf-note">Справочник участников ещё не получен от НРД — введите ИНН вручную.</span>{{end}}</div>
<div><label class="mf-req">ИНН принимающего депозитария</label><input name="recv_inn" id="recv_inn" value="{{.O.RecvINN}}" maxlength="10" inputmode="numeric"></div>
<div><label>Наименование</label><input name="recv_name" id="recv_name" value="{{.O.RecvName}}"></div>
<div><label>Счёт клиента у принимающего</label><input name="recv_account" value="{{.O.RecvAccount}}" placeholder="для сведения"></div>
<div><label>Место расчётов</label>
<select name="settle_place"><option value="nrd" {{if ne .O.SettlePlace "spb"}}selected{{end}}>НРД</option><option value="spb" {{if eq .O.SettlePlace "spb"}}selected{{end}}>СПБ Банк</option></select></div>
<div class="wide"><label style="display:flex;gap:8px;align-items:center;color:var(--text);font-size:13.5px"><input type="checkbox" name="cost_info" value="1" style="width:auto" {{if .O.CostInfo}}checked{{end}}> Передать сведения о расходах на приобретение (если клиент просил в поручении)</label></div>
</div>
<p class="mf-note">Бумаги списываются с нашего места хранения в НРД: {{if .NSD.DeponentCode}}депонент <code>{{.NSD.DeponentCode}}</code>, счёт <code>{{.NSD.AccountID}}</code>, раздел <code>{{.NSD.SectionID}}</code>{{else}}реквизиты не заданы в настройках НРД{{end}}.</p>
<div class="mf-h">5. Комментарий</div>
<div class="mf-grid"><div class="wide"><textarea name="comment" rows="2" placeholder="особые условия, контакты представителя и т.п.">{{.O.Comment}}</textarea></div></div>
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:16px">
<button type="submit" name="do" value="save" class="btn btn-ok">Сохранить</button>
{{if .DepoOn}}<button type="submit" name="do" value="depo" class="btn">Сохранить и сверить с DEPO</button>{{end}}
<a href="/admin/manual" class="btn btn-secondary">К списку</a>
</div>
</form>
<template id="secRow"><div class="mf-sec"><input name="sec_isin" placeholder="RU…" maxlength="12"><input name="sec_code" placeholder="необязательно"><input class="nm" name="sec_name" placeholder="заполнится из DEPO"><input name="sec_qty" inputmode="decimal"><button type="button" class="x" onclick="mfDel(this)" title="Убрать">×</button></div></template>
<script>
function mfAdd(){document.getElementById('secs').appendChild(document.getElementById('secRow').content.cloneNode(true))}
function mfDel(b){const rows=document.querySelectorAll('#secs .mf-sec');if(rows.length>1){b.parentNode.remove()}else{b.parentNode.querySelectorAll('input').forEach(i=>i.value='')}}
function mfPickHB(s){const o=s.options[s.selectedIndex];if(!o.value)return;document.getElementById('recv_inn').value=o.value;document.getElementById('recv_name').value=o.dataset.name||''}
</script>
{{end}}
@@ -13,7 +13,7 @@
<button data-sec="crypto"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>Криптография<span class="ind {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="nsd"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M4 21V10M20 21V10M3 10l9-6 9 6M8 21v-7M12 21v-7M16 21v-7"/></svg></span>НРД<span class="ind {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>Личный кабинет <span class="muted" style="font-size:10px;font-weight:500">ЕСИА Финанс</span><span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="tests"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Тесты</button>
<button data-sec="tests"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Диагностика</button>
<button data-sec="alerts"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>Оповещения<span class="ind {{if or .Settings.Alerts.SMTP.Enabled .Settings.Alerts.Zabbix.Enabled}}ok{{end}}"></span></button>
<button data-sec="update"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V8M6 12l6-6 6 6"/><path d="M5 4h14"/></svg></span>Обновления{{if .Settings.Update.Available}}<span class="ind warn"></span>{{end}}</button>
<button data-sec="license"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="12" r="4"/><path d="M12 12h9M18 12v3M15 12v2"/></svg></span>Лицензия{{if .License.Present}}<span class="ind {{if .License.Valid}}ok{{else}}err{{end}}"></span>{{end}}</button>
@@ -195,7 +195,7 @@
{{template "verline" (.Versions.Get "quik-mnp")}}
<div class="card">
<h2><span class="dot {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{else}}err{{end}}"></span>Подключение к БД МНП QUIK (MSSQL)</h2>
<p class="muted">При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД <strong>боевая</strong> — тест только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью <code>InstrEu</code>.<br>
<p class="muted">При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД <strong>боевая</strong> — проверка только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью <code>InstrEu</code>.<br>
<a href="/admin/help/quik"><strong>Инструкция для администратора QUIK</strong></a> — что настроить в QuikAdmin для M2M · <a href="/admin/docs/">документы ARQA</a>.</p>
<form method="post" action="/admin/setup/quik" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
<label>Сервер (host или host\instance)
@@ -228,7 +228,7 @@
<div class="card" id="sec-quik-cycle">
<h2><span class="dot {{if .Settings.Quik.Emulate}}warn{{else if not .Settings.Quik.WritesEnabled}}warn{{else if or .Settings.Quik.AutoOut .Settings.Quik.AutoIn}}ok{{else}}warn{{end}}"></span>Автоматический цикл M2M в МНП{{if .Settings.Quik.Emulate}} <span class="hero-status warn">{{if .DevMode}}имитация{{else}}остановлен{{end}}</span>{{end}}</h2>
<p class="muted">Что делает bj в Модуле неторговых поручений по ходу перевода: <strong>исходящий</strong> — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; <strong>входящий</strong> — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Проверено на боевом МНП 09.09.2026. Каждый шаг виден в карточке перевода и на странице <a href="/admin/quik">QUIK МНП</a>.</p>
<p class="muted">Что делает bj в Модуле неторговых поручений по ходу перевода: <strong>исходящий</strong> — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; <strong>входящий</strong> — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Каждый шаг виден в карточке перевода и на странице <a href="/admin/quik">QUIK МНП</a>.</p>
<form method="post" action="/admin/setup/quik/cycle" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
<input type="hidden" name="section" value="sec-quik-cycle">
<label style="display:flex;gap:8px;align-items:center;font-weight:600">
@@ -262,9 +262,9 @@
{{if .Settings.Quik.LastReport}}
<div class="card">
<h2>Полный ответ сервера (последний тест)</h2>
<h2>Полный ответ сервера (последняя проверка)</h2>
<p class="muted">Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK.
{{if not .Settings.Quik.LastAt.IsZero}}Тест от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC.{{end}}</p>
{{if not .Settings.Quik.LastAt.IsZero}}Проверка от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC.{{end}}</p>
<div style="display:flex;gap:8px;margin:8px 0">
<button type="button" class="btn" onclick="var t=document.getElementById('quik-report').textContent;navigator.clipboard&&navigator.clipboard.writeText(t);this.textContent='Скопировано ✓';setTimeout(()=>this.textContent='Скопировать отчёт',1500)">Скопировать отчёт</button>
</div>
@@ -397,10 +397,10 @@
<div class="card">
<h2><span class="dot {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
<p class="muted">Активный профиль: <code>{{if .Settings.Crypto.Profile}}{{.Settings.Crypto.Profile}}{{else}}—{{end}}</code> · провайдер <code>{{.Settings.Crypto.Provider}}</code>. Подробно — <a href="/admin/help/crypto">справка</a>.</p>
<p class="muted">Активный профиль: <code>{{if .Settings.Crypto.Profile}}{{.Settings.Crypto.Profile}}{{else}}—{{end}}</code> · провайдер {{if and (not .DevMode) (or (eq .Settings.Crypto.Provider "stub") (eq .Settings.Crypto.Provider ""))}}<code>не настроен</code>{{else}}<code>{{.Settings.Crypto.Provider}}</code>{{end}}. Подробно — <a href="/admin/help/crypto">справка</a>.</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
<form method="post" action="/admin/setup/crypto/check" style="margin:0"><button type="submit" class="btn btn-ok">✓ Проверить СКЗИ</button></form>
<form method="post" action="/admin/setup/crypto/test-sign" style="margin:0"><button type="submit" class="btn">✎ Тестовая подпись</button></form>
<form method="post" action="/admin/setup/crypto/test-sign" style="margin:0"><button type="submit" class="btn">✎ Пробная подпись</button></form>
<form method="post" action="/admin/setup/restart-crypto" style="margin:0" onsubmit="return confirm('Перезапустить crypto-service? Поднимется через ~5 сек.');"><button type="submit" class="btn btn-warn">↻ crypto-service</button></form>
<form method="post" action="/admin/setup/restart-server" style="margin:0" onsubmit="return confirm('Перезапустить bj-server? Через 5-10 сек страница вернётся.');"><button type="submit" class="btn btn-secondary">↻ bj-server</button></form>
</div>
@@ -409,7 +409,7 @@
<form method="post" action="/admin/setup/crypto" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
<label>Провайдер</label>
<select name="provider">
<option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>
{{if .DevMode}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}}
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
</select>
<label>Путь к модулю PKCS#11</label>
@@ -480,21 +480,22 @@
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="enabled" value="1" {{if .Settings.M2MAuto.Enabled}}checked{{end}}> Автоматический режим включён</label>
<button type="submit" class="btn btn-ok">Сохранить</button>
</form>
{{if not .Settings.M2MAuto.ChangedAt.IsZero}}<p class="muted" style="font-size:12px;margin-top:8px">{{if .Settings.M2MAuto.Enabled}}Включён{{else}}Выключен{{end}} {{msk .Settings.M2MAuto.ChangedAt}}{{with .Settings.M2MAuto.ChangedBy}} · {{.}}{{end}}.{{if .Settings.Quik.Emulate}} Шаги QUIK МНП — в режиме имитации.{{end}}</p>{{end}}
{{if not .Settings.M2MAuto.ChangedAt.IsZero}}<p class="muted" style="font-size:12px;margin-top:8px">{{if .Settings.M2MAuto.Enabled}}Включён{{else}}Выключен{{end}} {{msk .Settings.M2MAuto.ChangedAt}}{{with .Settings.M2MAuto.ChangedBy}} · {{.}}{{end}}.{{if .Settings.Quik.Emulate}}{{if .DevMode}} Шаги QUIK МНП — в режиме имитации.{{else}} Записи в QUIK МНП сейчас не выполняются.{{end}}{{end}}</p>{{end}}
</div>
<div class="card">
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span>Интеграционный шлюз (ИШ)</h2>
<p class="muted">{{if not .Settings.NSD.IGWBaseURL}}<code>mock-режим</code> — без реального ИШ.{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}} Стенды и установка — <a href="/admin/help/systems">справка</a>.</p>
<p class="muted">{{if not .Settings.NSD.IGWBaseURL}}{{if .DevMode}}<code>mock-режим</code> — без реального ИШ.{{else}}ИШ не настроен — обмен с НРД не выполняется.{{end}}{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}} Стенды и установка — <a href="/admin/help/systems">справка</a>.</p>
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
<label>Профиль / контур</label>
<select name="profile" id="nsd-profile">
<option value="" {{if eq .Settings.NSD.Profile ""}}selected{{end}}>— mock (демо) —</option>
<option value="guest-gost" {{if eq .Settings.NSD.Profile "guest-gost"}}selected{{end}}>guest-gost — GUEST, ГОСТ</option>
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>guest-rsa — GUEST, RSA</option>
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>test3-gost — TEST3, ГОСТ</option>
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>test3-rsa — TEST3, RSA</option>
<option value="prod-gost" {{if eq .Settings.NSD.Profile "prod-gost"}}selected{{end}}>prod-gost — ПРОМ, ГОСТ</option>
<option value="prod-rsa" {{if eq .Settings.NSD.Profile "prod-rsa"}}selected{{end}}>prod-rsa — ПРОМ, RSA</option>
{{/* mock и GUEST — для разработчиков; у клиента — только контуры НРД */}}
<option value="" {{if eq .Settings.NSD.Profile ""}}selected{{end}}>{{if .DevMode}}— mock (демо) —{{else}}— не выбран —{{end}}</option>
{{if or .DevMode (eq .Settings.NSD.Profile "guest-gost") (eq .Settings.NSD.Profile "guest-rsa")}} <option value="guest-gost" {{if eq .Settings.NSD.Profile "guest-gost"}}selected{{end}}>guest-gost — GUEST, ГОСТ</option>
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>guest-rsa — GUEST, RSA</option>{{end}}
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>test3-gost — тестовый контур НРД (TEST3), ГОСТ</option>
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>test3-rsa — тестовый контур НРД (TEST3), RSA</option>
<option value="prod-gost" {{if eq .Settings.NSD.Profile "prod-gost"}}selected{{end}}>prod-gost — боевой контур НРД, ГОСТ</option>
<option value="prod-rsa" {{if eq .Settings.NSD.Profile "prod-rsa"}}selected{{end}}>prod-rsa — боевой контур НРД, RSA</option>
</select>
<label>URL ONYX (WSL) НРД</label>
<input type="text" name="igw_base_url" id="nsd-url" value="{{.Settings.NSD.IGWBaseURL}}" placeholder="автозаполнится по профилю">
@@ -551,7 +552,7 @@
<input type="text" name="lk_public_url" value="{{.Settings.LK.PublicURL}}" placeholder="https://fontvielle.ef.winr.pro" style="flex:1">
{{if .Settings.LK.PublicURL}}<a class="btn btn-secondary" href="{{.Settings.LK.PublicURL}}" target="_blank" rel="noopener">Открыть →</a>{{end}}
</div>
<span class="muted" style="font-size:12px">Показывается ссылкой на дашборде для первичного теста.</span>
<span class="muted" style="font-size:12px">{{if .DevMode}}Показывается ссылкой на дашборде для первичного теста.{{else}}Адрес, по которому клиенты входят в личный кабинет.{{end}}</span>
</div>
<div>
<label>Callback URL <span class="muted" style="font-weight:400">(куда bj шлёт статус заявки)</span></label>
@@ -661,15 +662,15 @@
<div class="card">
<h2>Забрать заявки из ЛК</h2>
<p class="muted">Коннектор bj вычитывает заявки <code>depo_m2m</code> активного окружения в статусах «В очереди» и «В работе», которые ещё не обрабатывал, для каждой собирает M2M-пакет, отправляет в НРД и переводит заявку в статус <strong>«В работе»</strong>. По ответу НРД статус в ЛК замыкается автоматически (Исполнена / Отклонена). В режиме имитации НРД перед отправкой идёт сверка с DEPO: отказ сразу уходит в ЛК с кодом и причиной, одобренная заявка блокируется в QUIK и через 30 с получает имитированный ответ контрагента. Штатно это делает фоновый поллер раз в минуту — кнопка для ручного запуска.</p>
<p class="muted">Коннектор bj вычитывает заявки <code>depo_m2m</code> активного окружения в статусах «В очереди» и «В работе», которые ещё не обрабатывал, для каждой собирает M2M-пакет, отправляет в НРД и переводит заявку в статус <strong>«В работе»</strong>. По ответу НРД статус в ЛК замыкается автоматически (Исполнена / Отклонена).{{if .DevMode}} В режиме имитации НРД перед отправкой идёт сверка с DEPO: отказ сразу уходит в ЛК с кодом и причиной, одобренная заявка блокируется в QUIK и через 30 с получает имитированный ответ контрагента.{{end}} Штатно это делает фоновый поллер раз в минуту — кнопка для ручного запуска.</p>
<form method="post" action="/admin/setup/lk-pull" style="margin-top:12px"><button type="submit" class="btn">Забрать заявки сейчас</button></form>
<p class="muted" style="font-size:12px;margin-top:10px">Итог (сколько получено / отправлено, ошибки) появится вверху страницы.</p>
</div>
</section>
{{/* ============ ТЕСТЫ ============ */}}
{{/* ============ ДИАГНОСТИКА (id sec-tests сохранён для ссылок) ============ */}}
<section class="settings-section" id="sec-tests">
<h1>Тесты</h1>
<h1>Диагностика</h1>
<div class="card">
<h2>Здоровье сервиса</h2>
@@ -765,7 +766,7 @@
</div>
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<button type="submit" class="btn">Сохранить</button>
<button type="submit" formaction="/admin/setup/alerts-smtp-test" class="btn btn-secondary">Отправить тестовое письмо</button>
<button type="submit" formaction="/admin/setup/alerts-smtp-test" class="btn btn-secondary">Отправить проверочное письмо</button>
</div>
</form>
{{if .Settings.Alerts.SMTP.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.SMTP.LastMsg}}</p>{{end}}
@@ -784,7 +785,7 @@
</div>
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<button type="submit" class="btn">Сохранить</button>
<button type="submit" formaction="/admin/setup/alerts-zabbix-test" class="btn btn-secondary">Отправить тестовое значение</button>
<button type="submit" formaction="/admin/setup/alerts-zabbix-test" class="btn btn-secondary">Отправить проверочное значение</button>
</div>
</form>
{{if .Settings.Alerts.Zabbix.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}</p>{{end}}
@@ -935,7 +936,7 @@
<h1>Оформление отчётов</h1>
<div class="card">
<h2>Логотип организации в шапке отчётов</h2>
<p class="muted">Логотип показывается <b>справа</b> в шапке всех отчётов для регулятора (слева — логотип MOEX МОСТ / НРД) и на титульной сводке пакета тестов. Встраивается прямо в HTML — отчёт остаётся автономным. Если не загружать — используется стандартный логотип «Фонтвьель».</p>
<p class="muted">Логотип показывается <b>справа</b> в шапке всех отчётов для регулятора (слева — логотип MOEX МОСТ / НРД){{if .DevMode}} и на титульной сводке пакета тестов{{end}}. Встраивается прямо в HTML — отчёт остаётся автономным. Если не загружать — используется стандартный логотип «Фонтвьель».</p>
<div style="display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:14px 0;padding:14px;background:var(--card-2);border-radius:12px">
<div>
@@ -18,6 +18,7 @@
<p class="muted" style="margin-top: 16px">Проверка выполнена в {{.CheckedAt.Format "15:04:05 02.01.2006"}}.</p>
</div>
{{if .DevMode}}{{/* дорожная карта разработки — только в режиме разработки */}}
<div class="card">
<h2>Что подключается на следующих этапах</h2>
<table>
@@ -28,3 +29,4 @@
</table>
</div>
{{end}}
{{end}}
@@ -63,7 +63,7 @@
<form method="post" action="/admin/setup/postgres" style="margin-top:12px">
<label>DSN (строка подключения) <span class="tooltip" title="Формат: postgres://пользователь:пароль@хост:порт/база?sslmode=disable. Например: postgres://bj:secret@db.example.com:5432/bj?sslmode=require">?</span></label>
<input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:6px">
<p class="muted" style="margin-top:8px">При сохранении выполняется тестовое подключение (Ping). Если БД недоступна — будет ошибка.</p>
<p class="muted" style="margin-top:8px">При сохранении выполняется проверка подключения (Ping). Если БД недоступна — будет ошибка.</p>
<button type="submit" class="btn" style="margin-top:8px">Сохранить и проверить</button>
</form>
</details>
@@ -91,9 +91,9 @@
<summary style="cursor:pointer;color:var(--accent)">Параметры провайдера</summary>
<form method="post" action="/admin/setup/crypto" style="margin-top:12px;display:grid;gap:10px">
<div>
<label>Провайдер <span class="tooltip" title="validata — АПК «Валидата Клиент L»; stub — без криптографии (демо-режим без подписи)">?</span></label>
<label>Провайдер <span class="tooltip" title="validata — АПК «Валидата Клиент L»{{if .DevMode}}; stub — без криптографии (демо-режим без подписи){{end}}">?</span></label>
<select name="provider" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
<option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>
{{if .DevMode}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}}
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
</select>
</div>
@@ -186,19 +186,19 @@
<p>Адрес web-сервиса ONYX и имя ключевого контейнера НРД.</p>
<div class="help-block">
<strong>Что это?</strong> Интеграционный шлюз (ИШ) НРД — это компонент, через который наши M2M-сообщения отправляются в НРД. У НРД есть 4 контура: <em>GUEST</em> (для разработки) и <em>TEST3</em> (предпродакшен), каждый в варианте ГОСТ или RSA.<br>
<strong>Что это?</strong> Интеграционный шлюз (ИШ) НРД — это компонент, через который наши M2M-сообщения отправляются в НРД. {{if .DevMode}}У НРД есть 4 контура: <em>GUEST</em> (для разработки) и <em>TEST3</em> (предпродакшен), каждый в варианте ГОСТ или RSA.{{else}}У НРД есть тестовый контур (<em>TEST3</em>) и боевой контур, каждый в варианте ГОСТ или RSA.{{end}}<br>
<strong>Где взять?</strong> Дистрибутив ИШ и инструкции — на сайте НРД <a href="https://www.nsd.ru/workflow/system/programs/" target="_blank">nsd.ru/workflow/system/programs/</a>. Доступ к тестовым контурам выдаётся НРД по заявке (см. <code>DOC/instr_podkl_stend_v3.pdf</code>).
</div>
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px">
<div>
<label>Профиль <span class="tooltip" title="GUEST — гостевой контур для разработчиков (gost-gt.nsd.ru), TEST3 — тестовый предпродакшен (gost-t3.nsd.ru), prod — рабочий контур">?</span></label>
<label>Профиль <span class="tooltip" title="{{if .DevMode}}GUEST — гостевой контур для разработчиков (gost-gt.nsd.ru), {{end}}TEST3 — тестовый контур НРД (gost-t3.nsd.ru), prod — боевой контур НРД">?</span></label>
<select name="profile" id="nsd-profile" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>TEST3 · ГОСТ (рекомендуется для теста)</option>
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>TEST3 · RSA</option>
<option value="guest-gost" {{if eq .Settings.NSD.Profile "guest-gost"}}selected{{end}}>GUEST · ГОСТ</option>
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>GUEST · RSA</option>
<option value="prod" {{if eq .Settings.NSD.Profile "prod"}}selected{{end}}>prod — рабочий контур (осторожно)</option>
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>TEST3 · ГОСТ — тестовый контур НРД</option>
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>TEST3 · RSA — тестовый контур НРД</option>
{{if or .DevMode (eq .Settings.NSD.Profile "guest-gost") (eq .Settings.NSD.Profile "guest-rsa")}} <option value="guest-gost" {{if eq .Settings.NSD.Profile "guest-gost"}}selected{{end}}>GUEST · ГОСТ</option>
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>GUEST · RSA</option>{{end}}
<option value="prod" {{if eq .Settings.NSD.Profile "prod"}}selected{{end}}>prod — боевой контур НРД (осторожно)</option>
</select>
</div>
<div>
@@ -228,14 +228,15 @@
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=3" class="btn" style="background:var(--card);text-decoration:none">← К шагу 3</a>
{{if .Done.NSD}}<a href="/admin/wizard?step=5" class="btn" style="text-decoration:none">К шагу 5 →</a>{{else}}<a href="/admin/wizard?step=5&skip=nsd" class="btn" style="background:var(--card);text-decoration:none">Пропустить (mock-режим) →</a>{{end}}
{{if .Done.NSD}}<a href="/admin/wizard?step=5" class="btn" style="text-decoration:none">К шагу 5 →</a>{{else}}<a href="/admin/wizard?step=5&skip=nsd" class="btn" style="background:var(--card);text-decoration:none">Пропустить ({{if .DevMode}}mock-режим{{else}}обмен с НРД не настроен{{end}}) →</a>{{end}}
</div>
</div>
{{end}}
{{/* ============= ШАГ 5: Тест-ран ============= */}}
{{/* ============= ШАГ 5: Тест-ран (режим разработки) / проверка подключений ============= */}}
{{if eq .Step 5}}
<div class="card">
{{if .DevMode}}
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 5. Тестовая заявка</h2>
<p>Прогон полного цикла: создание заявки → валидация → подпись → отправка в НРД (или mock) → ожидание Decision → подтверждение.</p>
@@ -256,14 +257,19 @@
{{if .Settings.LastTest.Message}}<tr><td class="muted">Сообщение</td><td>{{.Settings.LastTest.Message}}</td></tr>{{end}}
</table>
{{end}}
{{else}}
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 5. Проверка подключений</h2>
<p>Проверьте, что база данных, СКЗИ и шлюз НРД доступны: на странице «Статус» bj проверяет каждое подключение и показывает, что нужно исправить.</p>
<a href="/admin/status" class="btn" style="background:var(--ok);font-size:15px;padding:10px 20px;text-decoration:none;display:inline-block;margin-top:12px">Открыть проверку подключений →</a>
{{end}}
<h3 style="margin-top:18px">Итоговая сводка</h3>
<table>
<tr><td class="muted">PostgreSQL</td><td>{{if .Done.Postgres}}<span style="color:var(--ok)">настроен</span>{{else}}<span class="muted">in-memory</span>{{end}}</td></tr>
<tr><td class="muted">Крипто-провайдер</td><td>{{if .Done.Crypto}}<span style="color:var(--ok)">{{.Settings.Crypto.Provider}}</span>{{else}}<span style="color:var(--err)">не настроен</span>{{end}}</td></tr>
<tr><td class="muted">Сертификатов установлено</td><td>{{len .Certs}}</td></tr>
<tr><td class="muted">ИШ НРД</td><td>{{if .Done.NSD}}<span style="color:var(--ok)">{{.Settings.NSD.Profile}}</span>{{else}}<span class="muted">mock-режим</span>{{end}}</td></tr>
<tr><td class="muted">Тестовый прогон</td><td>{{if .Done.TestRun}}<span style="color:var(--ok)">пройден</span>{{else}}<span class="muted">не запускался</span>{{end}}</td></tr>
<tr><td class="muted">ИШ НРД</td><td>{{if .Done.NSD}}<span style="color:var(--ok)">{{.Settings.NSD.Profile}}</span>{{else}}<span class="muted">{{if .DevMode}}mock-режим{{else}}не настроен{{end}}</span>{{end}}</td></tr>
{{if .DevMode}}<tr><td class="muted">Тестовый прогон</td><td>{{if .Done.TestRun}}<span style="color:var(--ok)">пройден</span>{{else}}<span class="muted">не запускался</span>{{end}}</td></tr>{{end}}
</table>
<div style="margin-top:20px;display:flex;justify-content:space-between">
+3 -3
View File
@@ -287,7 +287,7 @@ label { font-size:13px; font-weight:500; }
<a href="/admin/incoming">Входящие<span id="nav-incoming-badge" style="display:none;margin-left:5px;background:var(--err);color:#fff;border-radius:10px;padding:1px 7px;font-size:11px;font-weight:700"></span></a>
</span>
</span>
{{end}}{{if and .DevMode (can "operate")}}<a href="/admin/m2m-testing" class="{{if eq .Active "m2mtest"}}active{{end}}">Тестирование M2M</a>{{end}}
{{end}}{{if can "view"}}<a href="/admin/manual" class="{{if eq .Active "manual"}}active{{end}}">Ручной ввод M2M</a>{{end}}{{if and .DevMode (can "operate")}}<a href="/admin/m2m-testing" class="{{if eq .Active "m2mtest"}}active{{end}}">Тестирование M2M</a>{{end}}
{{if and .NPR (can "npr")}}<a href="/admin/npr" class="{{if eq .Active "npr"}}active{{end}}">НПР / УДС</a>{{end}}
{{if or (can "view") (can "quik")}}<span class="dd{{if or (eq .Active "fancy") (eq .Active "quik") (eq .Active "news") (eq .Active "handbook")}} active{{end}}">
<span class="ddbtn">Обмен ▾</span>
@@ -306,7 +306,7 @@ label { font-size:13px; font-weight:500; }
<div class="topbar-right">
{{with curUser}}<span class="user-chip" title="{{.RoleTitle}}{{if eq .Source "ad"}} · вход через AD{{end}}"><span class="user-name">{{if .Name}}{{.Name}}{{else}}{{.Login}}{{end}}</span><span class="user-role">{{.RoleTitle}}</span></span><a class="user-logout" href="/logout" title="Выйти">Выйти</a>{{else}}{{if authOpen}}<a class="user-logout" href="/admin/setup#users" title="Пользователи не созданы — вход открыт">без входа</a>{{end}}{{end}}
<span class="sys-status {{if .IsMockMode}}warn{{end}}" title="Состояние обмена с НРД">
<span class="pulse"></span>{{if .IsMockMode}}Эмуляция{{else}}НРД на связи{{end}}</span>
<span class="pulse"></span>{{if .IsMockMode}}{{if .DevMode}}Эмуляция{{else}}Нет связи с НРД{{end}}{{else}}НРД на связи{{end}}</span>
<span class="clock">{{.Now}}</span>
<button class="theme-toggle" id="theme-toggle" title="Светлая/тёмная тема" aria-label="Переключить тему"></button>
</div>
@@ -315,7 +315,7 @@ label { font-size:13px; font-weight:500; }
{{if .IsMockMode}}
<div class="banner-mock">
<span class="dot warn" style="width:9px;height:9px;margin:0"></span>
<div><strong style="color:var(--warn)">Режим эмуляции</strong> — реального обмена с НРД нет. <span class="muted">{{.MockReason}}</span></div>
<div><strong style="color:var(--warn)">{{if .DevMode}}Режим эмуляции{{else}}Обмен с НРД не настроен{{end}}</strong> — {{if .DevMode}}реального обмена с НРД нет{{else}}поручения в НРД не отправляются{{end}}. <span class="muted">{{.MockReason}}</span></div>
<a href="/admin/wizard" style="margin-left:auto">Открыть мастер настройки →</a>
</div>
{{end}}