feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
{{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}
|
||||
table.m2m{width:100%;border-collapse:collapse;font-size:13px}
|
||||
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}}
|
||||
|
||||
<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>Мостовиков Арсений Тимурович<div class="muted small">14.03.1999, г. Москва</div></td><td>паспорт 4519 № 736284<div class="muted small">ГУ МВД России по г. Москве, 28.03.2019, 770-093</div></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}}
|
||||
Reference in New Issue
Block a user