b0de7f2979
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
211 lines
17 KiB
HTML
211 lines
17 KiB
HTML
{{define "content"}}
|
||
<style>
|
||
.m2m-chips{display:flex;gap:8px;flex-wrap:wrap;margin:10px 0}
|
||
.m2m-chips span{padding:4px 12px;border-radius:20px;font-size:13px;font-weight:600;background:var(--card-2);border:1px solid var(--border)}
|
||
.m2m-chips .ok{background:var(--ok-weak,#e6f4ea);color:#1a7f37;border-color:#bfe3c9}
|
||
.m2m-chips .err{background:var(--err-weak,#fbe9e9);color:#b3261e;border-color:#f0c2bf}
|
||
.m2m-chips .prog{background:var(--warn-weak,#fbf3d9);color:#8a6d00;border-color:#ecd9a1}
|
||
.cpf{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin-top:8px}
|
||
.cpf label{font-size:12px;color:var(--muted);display:flex;flex-direction:column;gap:3px}
|
||
.cpf input{padding:6px 8px;border:1px solid var(--border);border-radius:7px;background:var(--card);color:var(--text);min-width:150px}
|
||
main{max-width:min(1680px,100%)} /* таблица сценариев широкая: страница шире общих 1200px, чтобы не было прокрутки вбок */
|
||
table.m2m{width:100%;border-collapse:collapse;font-size:13px}
|
||
table.m2m td{overflow-wrap:anywhere}
|
||
table.m2m th,table.m2m td{padding:6px 8px;border-bottom:1px solid var(--border);text-align:left;vertical-align:top}
|
||
table.m2m th{position:sticky;top:0;background:var(--card-2);font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}
|
||
table.m2m tr.st-passed{background:var(--ok-weak,#e6f4ea)}
|
||
table.m2m tr.st-failed{background:var(--err-weak,#fbe9e9)}
|
||
table.m2m tr.st-in_progress{background:var(--warn-weak,#fbf3d9)}
|
||
table.m2m tr.st-excluded{opacity:.45}
|
||
table.m2m tr.st-excluded td{color:var(--muted)}
|
||
.live{font-size:11px;margin-top:3px;font-weight:600}
|
||
table.m2m .i{color:var(--accent,#1f6feb);cursor:help;font-size:12px}
|
||
table.m2m tr:hover{outline:1px solid var(--accent-weak,#cfe0fb)}
|
||
.role-we{font-weight:700;color:var(--accent-strong,#1f4e79)}
|
||
.code{font-family:monospace;font-weight:700;color:#b3261e}
|
||
.small{font-size:11px}
|
||
.stf{display:inline-flex;gap:3px}
|
||
.stf button{border:1px solid var(--border);background:var(--card);border-radius:6px;padding:3px 7px;cursor:pointer;font-size:13px;line-height:1}
|
||
.stf button.b-ok:hover{background:#e6f4ea}
|
||
.stf button.b-err:hover{background:#fbe9e9}
|
||
.b-init{border:1px solid #1f4e79;background:var(--accent-weak,#e7f0fb);color:#1f4e79;border-radius:7px;padding:5px 10px;cursor:pointer;font-weight:600;font-size:12px;white-space:nowrap}
|
||
.b-init:hover{background:#d7e6fb}
|
||
.scen-row td{background:var(--card-2);font-weight:700;color:var(--text);font-size:12px}
|
||
.noteinp{width:100%;margin-top:3px;padding:3px 5px;border:1px solid var(--border);border-radius:5px;font-size:11px;background:var(--card);color:var(--text)}
|
||
</style>
|
||
|
||
<div class="card">
|
||
<h2>Тестирование M2M — с контрагентом (МБ / НРД)</h2>
|
||
<div style="padding:10px 14px;background:var(--accent-weak,#e7f0fb);border-radius:10px;margin:6px 0 10px;font-size:14px">
|
||
<strong>{{if .CounterpartyName}}{{.CounterpartyName}}{{else}}Контрагент 1{{end}}</strong> — <em>списывающий</em> (Контрагент 1) → <strong>Фонтвьель</strong> — <em>зачисляющий</em> (Контрагент 2, мы).
|
||
</div>
|
||
<p class="muted">По кейсам, где мы <em>Списывающий</em>, жмём «Инициировать отправку»; где <em>Зачисляющий</em> — отвечаем на странице <a href="/admin/incoming">Входящие</a> (для негативных кейсов — с кодом отказа). По инициированным кейсам статус и исход НРД <strong>подтягиваются автоматически</strong>. Статусы сохраняются между перезапусками.</p>
|
||
|
||
<div class="m2m-chips">
|
||
<span>Всего: {{.Total}}</span>
|
||
<span class="ok">✓ Успех: {{.Passed}}</span>
|
||
<span class="err">✗ Провал: {{.Failed}}</span>
|
||
<span class="prog">● В процессе: {{.Prog}}</span>
|
||
<span>○ Не начато: {{.Pending}}</span>
|
||
{{if .Excluded}}<span style="opacity:.6">⊘ Не тестируем: {{.Excluded}}</span>{{end}}
|
||
</div>
|
||
|
||
{{if or .Passed .Prog .Failed}}
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:10px 0 4px">
|
||
<a href="/admin/m2m-testing/report-bundle.zip" class="btn btn-ok" download title="ZIP: сводка + доказательства по каждому пройденному кейсу">⬇ Отчёты по тестам</a>
|
||
<a href="/admin/m2m-testing/fancy-bundle.zip" class="btn" download title="ZIP: входящие и исходящие обмены с контрагентом + наши решения — для программиста Fancy">⬇ Эталон для Fancy</a>
|
||
<form method="post" action="/admin/m2m-testing/depo-replay" style="margin:0;display:inline-flex;gap:6px;align-items:center" onsubmit="return confirm('Выгрузить проведённые переводы с выбранным контрагентом (решения, поручения 16/16-1, отчёты MS010) в трансферные таблицы DEPO? Джоба bj2depo создаст по ним поручения в тестовой базе депозитария. Повторный запуск ничего не дублирует.')"><input type="text" name="cp" value="{{.CounterpartyCode}}" title="код контрагента МОСТ (пусто = все)" style="width:130px;font-size:12px"><button type="submit" class="btn" title="FTV_M2M + FTV_M2M_LINES + FTV_M2M_EVENTS в нашей копии DEPO — чтобы увидеть, как тесты выглядят внутри депозитария">🗂 Выгрузить историю в DEPO</button></form>
|
||
<a href="/admin/m2m-testing/process" target="_blank" class="btn" title="Наглядная схема процесса M2M (16 / 16.1) для программиста">🔄 Как работает процесс</a>
|
||
<a href="/admin/m2m-testing/depo-sim" class="btn" title="Песочница: выбрать клиента/бумагу из DEPO, изменить поля и посмотреть, как ответит сверка (ничего не отправляется)">🧪 Имитация сверки с DEPO</a>
|
||
<a href="/admin/m2m-testing/spravka" target="_blank" class="btn" title="Расшифровка кодов ошибок M2M">📖 Коды ошибок</a>
|
||
<span class="muted" style="font-size:12px;flex-basis:100%;margin-top:2px">Архивы для отправки контрагенту / программисту Fancy. Наведите на кнопку — подсказка, что внутри.</span>
|
||
</div>
|
||
{{end}}
|
||
|
||
<div class="card" id="real-nsd" style="margin:10px 0;{{if .RealNSD.Enabled}}background:var(--warn-weak,#fbf3d9){{end}}">
|
||
<h3 style="margin:0 0 6px;font-size:15px">Тест «боевой ЛК → тестовый контур НРД» {{if .RealNSD.Enabled}}<span class="badge awaiting_decision">включён</span>{{else}}<span class="badge draft">выключен</span>{{end}}</h3>
|
||
<p class="muted" style="margin:0 0 8px;font-size:13px">Заявки клиентов из списка (из ЛК ЕСИА Финанс) проходят у нас всё по-настоящему: проверка шлюза и поручение в DEPO, блокировка и списание в QUIK МНП (<b>боевая запись</b>), статусы в ЛК. В НРД (TEST3) запрос уходит с данными тест-инвестора <b>{{.RealNSDAs}}</b> — GUID, референсы, бумаги и счета настоящие, ответы НРД применяются к заявке клиента. «Исполнена» в ЛК — по отчёту НРД об исполнении (MS010). Остальные заявки ЛК — как раньше (без отправки в НРД).</p>
|
||
{{if can "settings"}}<form method="post" action="/admin/setup/lk-real-nsd" class="cpf" onsubmit="return !this.enabled.checked || confirm('Включить? Для клиентов из списка QUIK МНП будет записывать по-настоящему (блокировка и списание бумаг).');">
|
||
<label style="flex-direction:row;align-items:center;gap:6px;font-size:13px;color:var(--text)"><input type="checkbox" name="enabled" style="min-width:0"{{if .RealNSD.Enabled}} checked{{end}}> включить</label>
|
||
<label style="min-width:280px">Паспорта клиентов (серия номер, через «;»)
|
||
<input name="docs" value="{{.RealNSD.Docs}}" placeholder="1110 754503"></label>
|
||
<button class="b-init" style="background:var(--card);border-color:var(--border);color:var(--text)">Сохранить</button>
|
||
</form>{{end}}
|
||
</div>
|
||
|
||
<form method="post" action="/admin/m2m-testing/counterparty" class="cpf" id="cpForm">
|
||
<label style="min-width:340px">Контрагент (из справочника M2M)
|
||
<input id="cp-search" list="cp-list" autocomplete="off" placeholder="начните вводить наименование или код…"
|
||
value="{{if .CounterpartyName}}{{.CounterpartyName}} ({{.CounterpartyCode}}){{end}}">
|
||
<datalist id="cp-list"></datalist>
|
||
</label>
|
||
<div class="muted small" id="cp-info" style="align-self:center">
|
||
{{if .CounterpartyCode}}Код: <code>{{.CounterpartyCode}}</code> · ИНН: {{if .CounterpartyINN}}{{.CounterpartyINN}}{{else}}—{{end}}{{else}}— выберите контрагента из списка —{{end}}
|
||
</div>
|
||
<input type="hidden" name="counterparty_name" id="cp-name" value="{{.CounterpartyName}}">
|
||
<input type="hidden" name="counterparty_code" id="cp-code" value="{{.CounterpartyCode}}">
|
||
<input type="hidden" name="counterparty_inn" id="cp-inn" value="{{.CounterpartyINN}}">
|
||
<input type="hidden" name="our_inn" value="{{.OurINN}}">
|
||
<button class="b-init" style="background:var(--card);border-color:var(--border);color:var(--text)">Сохранить</button>
|
||
</form>
|
||
<div style="margin-top:10px"><a href="/admin/m2m-testing/plan.html" class="btn" title="Самостоятельный HTML: участники, тестовые клиенты, остатки, кейсы с ожидаемыми ответами — для отправки команде контрагента">📄 План для контрагента (HTML)</a> <a href="/admin/m2m-testing/plan.html?inline=1" target="_blank" class="muted small">открыть в браузере</a></div>
|
||
<div class="muted small" style="margin-top:6px">Наш депозитарий: <strong>{{if .OurName}}{{.OurName}}{{else}}Фонтвьель{{end}}</strong> · код <code>{{.OurCode}}</code>{{if .OurINN}} · ИНН {{.OurINN}}{{end}} <span style="opacity:.7">(из справочника)</span></div>
|
||
</div>
|
||
<script>
|
||
(function(){
|
||
var HB = {{m2mHandbook}};
|
||
var dl = document.getElementById('cp-list'), search = document.getElementById('cp-search');
|
||
if(!Array.isArray(HB)||!dl||!search) return;
|
||
var byLabel = {};
|
||
HB.forEach(function(p){
|
||
var label = p.name + ' — ' + p.code + (p.inn?(' (ИНН '+p.inn+')'):'');
|
||
var o=document.createElement('option'); o.value=label; dl.appendChild(o);
|
||
byLabel[label]=p;
|
||
});
|
||
function resolve(){
|
||
var p = byLabel[search.value];
|
||
if(!p){
|
||
var v = search.value.trim().toLowerCase();
|
||
for(var i=0;i<HB.length;i++){ var x=HB[i];
|
||
if((x.code&&x.code.toLowerCase()===v)
|
||
||(x.inn&&x.inn.toLowerCase()===v)
|
||
||(x.name&&x.name.toLowerCase()===v)
|
||
||(x.name&&(x.name+' ('+x.code+')').toLowerCase()===v)){ p=x; break; } }
|
||
}
|
||
if(!p) return false;
|
||
document.getElementById('cp-name').value=p.name;
|
||
document.getElementById('cp-code').value=p.code;
|
||
document.getElementById('cp-inn').value=p.inn||'';
|
||
document.getElementById('cp-info').innerHTML='Код: <code>'+p.code+'</code> · ИНН: '+(p.inn||'—');
|
||
search.value=p.name+' ('+p.code+')';
|
||
return true;
|
||
}
|
||
search.addEventListener('change', resolve);
|
||
// на всякий случай резолвим и перед сохранением формы
|
||
document.getElementById('cpForm').addEventListener('submit', function(e){
|
||
if(search.value.trim()!=='' && !document.getElementById('cp-code').value){ resolve(); }
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<div class="card">
|
||
<h3 style="margin:0 0 8px">Данные для занесения в учётную систему</h3>
|
||
<div style="display:flex;gap:24px;flex-wrap:wrap">
|
||
<div>
|
||
<div class="muted small" style="text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Тестовые клиенты</div>
|
||
<table class="m2m" style="font-size:12px">
|
||
<tr><th></th><th>ФИО</th><th>ДУЛ</th></tr>
|
||
<tr><td><strong>К1</strong></td><td>Бубликов Павел Иванович</td><td>паспорт 3453 № 289090</td></tr>
|
||
<tr><td><strong>К2</strong></td><td>Баранкин Сергей Сергеевич</td><td>паспорт 3763 № 290089 <span class="muted">(кейсы M2M04)</span></td></tr>
|
||
</table>
|
||
</div>
|
||
<div>
|
||
<div class="muted small" style="text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Бумаги</div>
|
||
<table class="m2m" style="font-size:12px">
|
||
<tr><th>ISIN</th><th>Код НРД</th><th>Наименование</th></tr>
|
||
<tr><td>RU0009029540</td><td>SBER/03</td><td>Акции об. ПАО Сбербанк</td></tr>
|
||
<tr><td>RU000A0JUR61</td><td>RU000A0JUR61</td><td>Пай ИПИФ «КОНСЕРВАТОРия»</td></tr>
|
||
<tr><td>RU000A102QL3</td><td>RU000A102QL3</td><td>Облигация суборд. ВТБ (СУБ-Т1-2)</td></tr>
|
||
<tr><td>RU0007661625</td><td>RU0007661625</td><td>Акции об. ПАО «Газпром» <span class="muted">(M2M05)</span></td></tr>
|
||
<tr><td>XS0088543193</td><td>XS0088543193</td><td>Минфин РФ (USD) <span class="muted">(M2M01, не-RU)</span></td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="overflow-x:auto">
|
||
<table class="m2m">
|
||
<thead><tr>
|
||
<th>#</th><th>Списывающий</th><th>Зачисляющий</th><th>Наша роль</th>
|
||
<th>Клиент</th><th>Расч.депо</th><th>Бумаги</th><th>Ожидание</th>
|
||
<th>Статус</th><th>Действие</th>
|
||
</tr></thead>
|
||
<tbody>
|
||
{{$scen := ""}}
|
||
{{range .Rows}}
|
||
{{if ne .Scenario $scen}}
|
||
<tr class="scen-row"><td colspan="10">Сценарий {{.Scenario}}</td></tr>
|
||
{{$scen = .Scenario}}
|
||
{{end}}
|
||
<tr class="st-{{.StatusClass}}" title="{{.Desc}}">
|
||
<td><strong>{{.ID}}</strong> <span class="i" title="{{.Desc}}">ⓘ</span></td>
|
||
<td>{{.Debit}}</td>
|
||
<td>{{.Credit}}</td>
|
||
<td class="{{if .WeInitiate}}role-we{{else}}role-we{{end}}">{{.OurRole}}</td>
|
||
<td>{{.Client}}</td>
|
||
<td>{{.SettleDeb}}→{{.SettleCred}}</td>
|
||
<td class="small">{{.SecList}}</td>
|
||
<td>{{if .Expect}}<span class="code">{{.Expect}}</span>{{else}}<span class="ok" style="color:#1a7f37">успех</span>{{end}}<div class="muted small">{{.Note}}</div></td>
|
||
<td>
|
||
{{if .Excluded}}
|
||
<span class="muted small">⊘ не тестируем</span>
|
||
{{else}}
|
||
<form method="post" action="/admin/m2m-testing/status" class="stf">
|
||
<input type="hidden" name="id" value="{{.ID}}">
|
||
<button class="b-ok" name="status" value="passed" title="Успех">✓</button>
|
||
<button class="b-err" name="status" value="failed" title="Провал">✗</button>
|
||
<button name="status" value="reset" title="Сброс">○</button>
|
||
</form>
|
||
{{if .Live}}<div class="live {{if eq .StatusClass "passed"}}ok{{else if eq .StatusClass "failed"}}err{{else}}muted{{end}}" style="{{if eq .StatusClass "passed"}}color:#1a7f37{{else if eq .StatusClass "failed"}}color:#b3261e{{end}}">⟳ {{.Live}}</div>{{end}}
|
||
{{if .State.Note}}<div class="muted small">{{.State.Note}}</div>{{end}}
|
||
{{if .State.GUID}}<div class="muted small">GUID: {{.State.GUID}}</div>{{end}}
|
||
{{end}}
|
||
</td>
|
||
<td>
|
||
{{if .Excluded}}
|
||
<span class="muted small">—</span>
|
||
{{else if .WeInitiate}}
|
||
{{if .Secs}}
|
||
<a href="/admin/m2m-testing/preview?id={{.ID}}" class="b-init" style="text-decoration:none;display:inline-block">▶ Инициировать</a>
|
||
{{else}}<span class="muted small">Сценарий 2 — формируется вручную</span>{{end}}
|
||
{{else}}
|
||
<a href="/admin/incoming" class="muted small">Ответить во «Входящих»{{if .Expect}}<br>код <span class="code">{{.Expect}}</span>{{end}}</a>
|
||
{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{end}}
|