a13c9d6184
- quikDetectAccounts: Export не имеет колонки QuikFirm (ошибка 207 Invalid column name) — запросы по Actions/Export выполняются по отдельности, источник без нужной колонки пропускается. - «Полный ответ сервера (последняя проверка)» свёрнут в details, чтобы не прятать настройки ниже. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
173 lines
19 KiB
HTML
173 lines
19 KiB
HTML
{{/* «QUIK — модуль неторговых поручений» — один и тот же в настройках и в мастере (данные — SetupData). */}}
|
||
{{define "quikSection"}}
|
||
{{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>Подключение к модулю неторговых поручений</h2>
|
||
<p class="muted">{{if not .InWizard}}bj блокирует бумаги клиента в QUIK на время перевода через модуль неторговых поручений (МНП). {{end}}Нужен логин SQL с ролью <code>InstrEu</code> — его выдаёт администратор QUIK.</p>
|
||
{{template "quikMemo" .}}
|
||
<form method="post" action="/admin/setup/quik" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||
<input type="hidden" name="check" value="1">
|
||
<div style="display:grid;grid-template-columns:2.2fr .8fr 1.6fr;gap:10px">
|
||
<label>Сервер QUIK МНП
|
||
<input type="text" name="server" data-need="адрес сервера QUIK МНП" value="{{.Settings.Quik.Server}}" placeholder="адрес или имя\экземпляр" style="width:100%" required></label>
|
||
<label>Порт
|
||
<input type="number" name="port" value="{{if .Settings.Quik.Port}}{{.Settings.Quik.Port}}{{else}}1433{{end}}" style="width:100%"></label>
|
||
<label>База МНП{{/* без имени базы модуль не подключается — обязательно */}}
|
||
<input type="text" name="database" data-need="имя базы модуля неторговых поручений" value="{{.Settings.Quik.Database}}" placeholder="имя базы модуля" style="width:100%" required spellcheck="false"></label>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<label>Логин
|
||
<input type="text" name="user" data-need="логин SQL для QUIK" value="{{.Settings.Quik.User}}" style="width:100%" required autocomplete="off"></label>
|
||
<label>Пароль
|
||
<input type="password" name="password" {{if not .Settings.Quik.Password}}data-need="пароль SQL для QUIK"{{end}} placeholder="{{if .Settings.Quik.Password}}сохранён — пусто, чтобы не менять{{end}}" autocomplete="new-password" style="width:100%"></label>
|
||
</div>
|
||
<details {{if quikNeedsTrust .Settings.Quik}}open{{end}}><summary style="cursor:pointer;color:var(--accent);font-size:13px">Дополнительно: шифрование</summary>
|
||
<div style="display:grid;gap:10px;margin-top:8px">
|
||
<label>Шифрование
|
||
<select name="encrypt" style="width:100%">
|
||
<option value="true" {{if ne .Settings.Quik.Encrypt "disable"}}selected{{end}}>Шифровать соединение (обычно требуется)</option>
|
||
<option value="disable" {{if eq .Settings.Quik.Encrypt "disable"}}selected{{end}}>Без шифрования</option>
|
||
</select></label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400">
|
||
<input type="checkbox" name="trust" value="1" {{if or .Settings.Quik.Trust (not .Settings.Quik.Server)}}checked{{end}}>{{/* по умолчанию — да: у QUIK сертификат свой */}}
|
||
Доверять сертификату сервера, даже самоподписанному</label>
|
||
</div>
|
||
</details>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button> <span class="muted" style="font-size:12.5px;margin-left:6px">проверка только читает — в QUIK ничего не меняется</span></div>
|
||
</form>
|
||
<p class="muted" style="margin-top:12px">Статус: {{if .Settings.Quik.Verified}}<span style="color:var(--ok)">✓ подключение проверено</span>{{else}}<span class="muted">не проверено</span>{{end}}</p>
|
||
</div>
|
||
|
||
{{if not .InWizard}}{{/* в мастере вместо этой карточки — выбор «Как bj работает с QUIK» (шаг QUIK) */}}
|
||
{{if .Settings.Quik.Verified}}{{template "quikModeCard" (withRet . "")}}{{end}}
|
||
<div class="card" id="sec-quik-cycle-more">
|
||
<details class="adv-box" {{if quikModeCustom .Settings.Quik}}open{{end}}>
|
||
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">отдельно исходящие и входящие, инструменты МНП, коды клиентов — нужны, только если QUIK настроен необычно</span></summary>
|
||
<div id="sec-quik-cycle">
|
||
<h3 class="adv-h"><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 devOn}}имитация{{else}}остановлен{{end}}</span>{{end}}</h3>
|
||
<p class="muted">Обычно всё задаёт выбор «Только чтение / Чтение и запись» выше. Здесь — если нужно иначе: например, исходящие вести автоматически, а входящие зачислять вручную; или инструменты МНП в QUIK названы не по умолчанию.</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">
|
||
<input type="checkbox" name="writes_enabled" value="1" {{if .Settings.Quik.WritesEnabled}}checked{{end}}>
|
||
Разрешить записи в МНП (главный выключатель: без него bj в QUIK ничего не меняет)</label>
|
||
{{if devOn}} <label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||
<input type="checkbox" name="emulate" value="1" {{if .Settings.Quik.Emulate}}checked{{end}}>
|
||
Имитация: шаги цикла (блокировка, подпись, снятие, зачисление) проходят и отмечаются «имитация», в МНП ничего не пишется — пока нет пользователя QUIK для блокировок и лимитов. Имеет приоритет над записью.</label>{{end}}
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||
<input type="checkbox" name="auto_out" value="1" {{if .Settings.Quik.AutoOut}}checked{{end}}>
|
||
Исходящие переводы: регистрировать при отправке, подписывать / снимать по ответу автоматически</label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||
<input type="checkbox" name="auto_in" value="1" {{if .Settings.Quik.AutoIn}}checked{{end}}>
|
||
Входящие переводы: зачислять в QUIK после приёма поручения НРД автоматически</label>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<label>МНП-инструмент вывода
|
||
<input type="text" name="sec_out" value="{{.Settings.Quik.SecOut}}" placeholder="пусто — по классу INSTROUTM2M" style="width:100%"></label>
|
||
<label>МНП-инструмент ввода
|
||
<input type="text" name="sec_in" value="{{.Settings.Quik.SecIn}}" placeholder="пусто — по классу INSTRINM2M" style="width:100%"></label>
|
||
</div>
|
||
<label>Исключения кодов клиентов <span class="muted">— обычно не нужны: номер портфеля «БР-001» сам становится кодом «BR001»; иначе строкой «номер портфеля=код QUIK»</span>
|
||
<textarea name="client_code_map" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:12px" placeholder="БР-12-3=BR123">{{quikCodeMap .Settings.Quik.ClientCodeMap}}</textarea></label>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn">Сохранить</button></div>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if .Settings.Quik.LastReport}}
|
||
<div class="card">
|
||
<details>
|
||
<summary style="cursor:pointer;font-weight:600;font-size:16px">Полный ответ сервера (последняя проверка){{if not .Settings.Quik.LastAt.IsZero}} · проверка от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC{{end}}</summary>
|
||
<p class="muted" style="margin-top:8px">Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK.</p>
|
||
<div style="display:flex;gap:8px;margin:8px 0">
|
||
<button type="button" class="btn" onclick="var t=document.getElementById('quik-report').textContent;window.bjCopy(t);this.textContent='Скопировано ✓';setTimeout(()=>this.textContent='Скопировать отчёт',1500)">Скопировать отчёт</button>
|
||
</div>
|
||
<pre id="quik-report" style="white-space:pre-wrap;word-break:break-word;background:var(--surface-2,#0d1117);color:var(--code-fg,#c9d1d9);padding:14px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;max-height:420px;user-select:all">{{.Settings.Quik.LastReport}}</pre>
|
||
</details>
|
||
</div>
|
||
{{end}}
|
||
{{end}}
|
||
|
||
{{define "mnpCheck"}}
|
||
<div class="card" id="mnp-check">
|
||
<h2><span class="dot" id="mnp-dot"></span>Проверка модуля неторговых поручений</h2>
|
||
<p class="muted" style="margin:0 0 8px">Только чтение: всё, без чего цикл M2M в МНП не работает. Ничего в QUIK не меняет.</p>
|
||
<table style="width:100%"><tbody id="mnp-rows"></tbody></table>
|
||
<p style="margin-top:8px"><button type="button" class="btn" id="mnp-run">Проверить МНП</button> <span class="muted" id="mnp-at" style="font-size:12px"></span></p>
|
||
</div>
|
||
<script>
|
||
(function(){
|
||
var rows=document.getElementById('mnp-rows'), dot=document.getElementById('mnp-dot');
|
||
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||
var ic={ok:'<span style="color:var(--ok)">✓</span>',info:'<span style="color:var(--accent)">ℹ</span>',warn:'<span style="color:var(--warn)">⚠</span>',err:'<span style="color:var(--err)">✗</span>'};
|
||
function run(){ rows.innerHTML='<tr><td class="muted">Проверяю…</td></tr>';
|
||
fetch('/admin/setup/quik/mnp-check',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
|
||
rows.innerHTML=d.checks.map(function(c){return '<tr><td style="width:22px;vertical-align:top">'+(ic[c.state]||'')+'</td><td style="vertical-align:top;padding-right:10px;white-space:nowrap"><b>'+esc(c.title)+'</b></td><td style="vertical-align:top">'+esc(c.detail)+'</td></tr>'}).join('');
|
||
dot.className='dot '+d.state; document.getElementById('mnp-at').textContent='проверено '+d.at;
|
||
}).catch(function(){ rows.innerHTML='<tr><td>Проверка не выполнилась</td></tr>'; }); }
|
||
document.getElementById('mnp-run').onclick=run;
|
||
{{if .Settings.Quik.Server}}run();{{end}}
|
||
})();
|
||
</script>
|
||
{{end}}
|
||
|
||
{{/* Памятка администратору QUIK — в мастере и в «Настройке». Без ссылок на техническую справку (её нет в
|
||
рабочей сборке) и на документы ARQA (их у клиента нет на сервере bj — они у администратора QUIK). */}}
|
||
{{define "quikMemo"}}
|
||
<details style="margin:2px 0 10px;font-size:13.5px"><summary style="cursor:pointer;color:var(--accent)">Памятка администратору QUIK — что настроить</summary>
|
||
<p style="margin:6px 0 4px">Всё это описано в документации ARQA на модуль неторговых поручений («Руководство администратора», «Таблицы и процедуры БД») — она есть у администратора QUIK:</p>
|
||
<ul style="margin:0 0 6px;padding-left:20px">
|
||
<li>в QAdmin — классы <code>INSTROUTM2M</code> (вывод ценных бумаг) и <code>INSTRINM2M</code> (ввод), типы неторговых поручений M2M;</li>
|
||
<li>SQL-логин для bj с ролью <code>InstrEu</code> — его логин и пароль вписываются в поля ниже;</li>
|
||
<li>справочник депозитариев-получателей <code>M2MDepoRecipients</code> заполняет bj; после первого заполнения модуль нужно перезапустить — QUIK читает справочник только при запуске.</li>
|
||
</ul>
|
||
{{if internalDocs}}<a href="/admin/help/quik">Подробная внутренняя справка →</a>{{end}}
|
||
</details>
|
||
{{end}}
|
||
|
||
{{/* Как bj работает с QUIK: только чтение или чтение и запись (боевая работа) — одинаково в мастере (шаг 5) и в
|
||
Настройка → QUIK МНП. Данные — withRet: .D — данные страницы (нужно .D.Settings), .Ret — куда вернуться без JS. */}}
|
||
{{define "quikModeCard"}}
|
||
<div class="card" id="quik-mode">
|
||
<h2>Как bj работает с QUIK</h2>
|
||
<form method="post" action="/admin/setup/quik/mode" id="quik-mode-form" class="dq">
|
||
{{if .Ret}}<input type="hidden" name="return_to" value="{{.Ret}}">{{end}}
|
||
<div class="dq-tiles">
|
||
<label class="dq-tile"><input type="radio" name="mode" value="check" {{if not .D.Settings.Quik.WritesEnabled}}checked{{end}}><span><b>Только чтение</b><small>bj подключён к QUIK и читает данные, но ничего в нём не меняет: не блокирует и не разблокирует бумаги, не меняет лимиты. Для настройки и проверки — когда будете готовы, включите «Чтение и запись».</small></span></label>
|
||
<label class="dq-tile"><input type="radio" name="mode" value="auto" {{if .D.Settings.Quik.WritesEnabled}}checked{{end}}><span><b>Чтение и запись</b><small>Боевая работа: bj сам блокирует бумаги в QUIK при отправке перевода, списывает или снимает блокировку по ответу НРД, зачисляет входящие.</small></span></label>
|
||
</div>
|
||
<div class="dq-jobname" id="quik-acc">
|
||
<label>Торговый счёт для поручений<input type="text" name="account" value="{{.D.Settings.Quik.Account}}" placeholder="торговый счёт фирмы в QUIK" spellcheck="false" class="dq-mono"></label>
|
||
<button type="button" id="quik-acc-detect" class="btn">Определить из QUIK</button>
|
||
<p class="dq-note" style="margin:4px 0 0">bj возьмёт торговый счёт фирмы из истории поручений в БД QUIK (таблицы Actions/Export). Если счёт один — подставит сам.</p>
|
||
</div>
|
||
<label class="dq-alt quik-term"><input type="checkbox" name="terminal" value="1" {{if .D.Settings.Quik.PollTerminal}}checked{{end}}><span><b>Клиенты подают поручения на перевод из QUIK</b> — из терминала или мобильного приложения («Вывод ЦБ (M2M)»). bj сам замечает такие поручения (раз в 30 секунд) и сообщает оператору; бумаги QUIK блокирует сразу при подаче. Если клиенты подают поручения только через депозитарий — оставьте выключенным.</span></label>
|
||
<input type="hidden" name="terminal_set" value="1">
|
||
{{with quikModeCustom .D.Settings.Quik}}<p class="dq-warn">{{.}} — изменено в «Расширенных настройках» ниже.</p>{{end}}
|
||
<p class="dq-note" id="quik-mode-st" aria-live="polite">Выбор сохраняется сразу.{{if .Ret}} Тонкая настройка — инструменты МНП, коды клиентов, поручения из терминала — в «Настройка → QUIK».{{end}}</p>
|
||
<noscript><button type="submit" class="btn">Сохранить</button></noscript>
|
||
</form>
|
||
<p class="dq-note">{{quikRecipientsInfo}} После первого заполнения справочника депозитариев-получателей попросите администратора QUIK перезапустить модуль неторговых поручений — QUIK читает справочник только при запуске.</p>
|
||
</div>
|
||
<script>(function(){
|
||
var f=document.getElementById('quik-mode-form'), st=document.getElementById('quik-mode-st'), acc=document.getElementById('quik-acc');
|
||
function sync(){ acc.hidden=f.mode.value!=='auto'; }
|
||
f.addEventListener('change', function(){ sync();
|
||
fetch(f.action,{method:'POST',body:new URLSearchParams(new FormData(f)),headers:{'Content-Type':'application/x-www-form-urlencoded','X-BJ-Ajax':'1'},cache:'no-store'})
|
||
.then(function(r){ return r.text().then(function(t){ st.textContent=(r.ok?'✓ Сохранено: ':'✗ ')+t; }); })
|
||
.catch(function(){ st.textContent='✗ Не сохранилось — обновите страницу и выберите ещё раз.'; });
|
||
});
|
||
f.addEventListener('submit', function(e){ if(window.fetch) e.preventDefault(); });
|
||
var db=document.getElementById('quik-acc-detect');
|
||
if(db) db.addEventListener('click', function(){
|
||
st.textContent='Определяю торговый счёт из QUIK…';
|
||
fetch('/admin/setup/quik/detect-account',{method:'POST',headers:{'X-BJ-Ajax':'1'},cache:'no-store'})
|
||
.then(function(r){ return r.text(); })
|
||
.then(function(t){ st.textContent=t; var m=t.match(/—\s*(\S+)\s*$/); if(m){ f.account.value=m[1]; } })
|
||
.catch(function(){ st.textContent='Не удалось определить — обновите страницу.'; });
|
||
});
|
||
sync();
|
||
})();</script>
|
||
{{end}}
|