Files
Bridge-and-Join-s/internal/lkgateway/web/templates/admin_manual.html
T

430 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<style>
.mf-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px 14px}
/* Ячейка — колонка: подпись сверху, поле прижато к низу. Так поля одной строки стоят на одной
линии, даже если у какой-то подписи две строки. */
.mf-grid>div{display:flex;flex-direction:column;justify-content:flex-end}
.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-find{background:var(--card-2);border:1px solid var(--border);border-radius:10px;padding:10px;margin-bottom:10px}
.mf-find input{padding:7px 9px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);font-size:13.5px}
.mf-res:empty{display:none}.mf-res{margin:8px 0}
.mf-hit{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center;padding:7px 0;border-top:1px solid var(--border)}
.mf-pos{display:block;width:100%;text-align:left;padding:7px 10px;margin:3px 0;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;font-size:13px}
.mf-pos:hover:not([disabled]){border-color:var(--accent)}.mf-pos[disabled]{opacity:.5;cursor:not-allowed}
.hb-box{position:relative}
.hb-res{margin-top:4px;max-height:320px;overflow:auto;border-radius:8px;border:1px solid var(--border);background:var(--card-2);padding:4px}
.hb-res:empty{display:none}
.hb-wrap{display:flex;gap:6px}.hb-wrap input{flex:1}
.hb-toggle{padding:0 12px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}
.hb-hit{display:block;width:100%;text-align:left;padding:7px 10px;margin:2px 0;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;font-size:13px}
.hb-hit:hover{border-color:var(--accent)}
.hb-m{color:var(--muted);font-size:12px}
.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 $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>
{{if .DepoOn}}
<div class="mf-find">
<div style="display:flex;gap:8px;flex-wrap:wrap">
<input id="dq" placeholder="Фамилия (или ФИО), либо серия и номер документа" style="flex:1;min-width:260px" onkeydown="if(event.key==='Enter'){event.preventDefault();mfFind()}">
<button type="button" class="btn" onclick="mfFind()">Найти в DEPO</button>
</div>
<div id="dres" class="mf-res"></div>
</div>
{{end}}
<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><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" id="f_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 id="dpos" class="mf-res"></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>
{{$sec := index .O.Securities 0}}
<div class="mf-sec"><input name="sec_isin" id="f_isin" value="{{$sec.ISIN}}" placeholder="RU…" maxlength="12"><input name="sec_code" value="{{$sec.Code}}" placeholder="необязательно"><input class="nm" name="sec_name" id="f_sname" value="{{$sec.Name}}" placeholder="заполнится из DEPO"><input name="sec_qty" id="f_qty" value="{{$sec.Qty}}" inputmode="decimal"><span></span></div>
<p class="mf-note">Одно поручение — одна бумага: на каждую бумагу оформляется отдельное поручение. Через MOEX МОСТ переводятся только российские выпуски (ISIN начинается с RU). Дробное количество — для паёв, через точку или запятую.</p>
<div class="mf-h">4. Принимающая сторона</div>
<div class="mf-grid">
<div class="wide"><label>Найти в справочнике участников M2M</label>
<div class="hb-wrap"><input id="hbq" placeholder="Выберите из списка или введите ИНН, название, код" autocomplete="off"><button type="button" class="hb-toggle" title="Весь список">▾</button></div>
<div id="hbres" class="hb-res"></div>
{{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 value="НРД" disabled></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>
<script>
const mfEsc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
let mfFaces=[];
function mfSet(n,v){const e=document.querySelector('#mf [name="'+n+'"]');if(e&&v!==undefined&&v!==null)e.value=v}
async function mfFind(){
const q=document.getElementById('dq').value.trim(),box=document.getElementById('dres');
box.innerHTML='<span class="mf-note">Поиск…</span>';
try{const r=await (await fetch('/admin/manual/depo/search?q='+encodeURIComponent(q))).json();
if(r.error){box.innerHTML='<span class="dc-bad">'+mfEsc(r.error)+'</span>';return}
mfFaces=r.items||[];
box.innerHTML=mfFaces.map((f,i)=>'<div class="mf-hit"><div><b>'+mfEsc(f.name)+'</b><div class="mf-note">'+(f.birth?'д.р. '+mfEsc(f.birth)+' · ':'')+'документ '+mfEsc(f.doc_ser)+' '+mfEsc(f.doc_num)+'</div></div><div style="display:flex;gap:6px;flex-wrap:wrap">'+
(f.accounts||[]).map((a,j)=>'<button type="button" class="btn btn-secondary" onclick="mfPick('+i+','+j+')"'+(a.iis?' title="ИИС — перевод через M2M не выполняется"':'')+'>'+mfEsc(a.number)+(a.contract?' · '+mfEsc(a.contract):'')+(a.iis?' · ИИС':'')+'</button>').join('')+'</div></div>').join('');
}catch(e){box.innerHTML='<span class="dc-bad">Поиск не выполнен: '+mfEsc(e)+'</span>'}
}
async function mfPick(i,j){
const f=mfFaces[i],a=f.accounts[j];
mfSet('last_name',f.last_name);mfSet('first_name',f.first_name);mfSet('middle_name',f.middle_name||'');mfSet('birth_date',f.birth||'');
if(f.doc_type)mfSet('doc_type',f.doc_type);
mfSet('doc_series',f.doc_ser||'');mfSet('doc_number',f.doc_num);mfSet('doc_issue_date',f.doc_date||'');mfSet('doc_issuer',f.doc_issuer||'');
mfSet('account',a.number);mfSet('contract',a.contract||'');if(f.phone)mfSet('phone',f.phone);
document.getElementById('dres').innerHTML='<span class="dc-ok">Подставлено из DEPO: '+mfEsc(f.name)+', счёт '+mfEsc(a.number)+'. Сверьте с поручением клиента.</span>';
const box=document.getElementById('dpos');box.innerHTML='<span class="mf-note">Загрузка бумаг клиента…</span>';
try{const r=await (await fetch('/admin/manual/depo/positions?client='+a.client_id)).json();
if(r.error){box.innerHTML='<span class="dc-bad">'+mfEsc(r.error)+'</span>';return}
const items=r.items||[];mfPos=items;
if(!items.length){box.innerHTML='<span class="mf-note">На торговом разделе счёта '+mfEsc(a.number)+' бумаг нет.</span>';return}
box.innerHTML='<div class="mf-note" style="margin-bottom:4px">Бумаги на торговом разделе '+mfEsc(a.number)+' — выберите одну:</div>'+items.map((p,k)=>'<button type="button" class="mf-pos"'+(p.russian?'':' disabled title="иностранный выпуск — через M2M не переводится"')+' onclick="mfSec('+k+')"><b>'+mfEsc(p.isin)+'</b> '+mfEsc(p.name)+' <span class="mf-note">· '+p.qty+' шт.</span></button>').join('');
}catch(e){box.innerHTML='<span class="dc-bad">'+mfEsc(e)+'</span>'}
}
let mfPos=[];
function mfSec(k){const p=mfPos[k];document.getElementById('f_isin').value=p.isin;document.getElementById('f_sname').value=p.name;const q=document.getElementById('f_qty');q.placeholder='доступно '+p.qty;q.focus()}
// hbFind — подбор участника справочника НРД по ИНН, названию или коду (без учёта регистра и кавычек).
function hbNorm(s){return String(s||'').toLowerCase().replace(/ё/g,'е').replace(/[«»"'“”.,()]/g,' ').replace(/\s+/g,' ').trim()}
function hbFind(list,q){q=hbNorm(q);
if(!q)return list.slice().sort((a,b)=>(a.kind===b.kind?0:a.kind==='depo'?-1:1)||hbNorm(a.name).localeCompare(hbNorm(b.name),'ru'));
const words=q.split(' ');
return list.map(p=>{const hay=hbNorm([p.inn,p.code,p.name,p.full_name,p.display_name,p.eng_name].join(' '));
if(!words.every(w=>hay.includes(w)))return null;
const score=(String(p.inn).startsWith(q)||String(p.code).toLowerCase().startsWith(q)?0:hbNorm(p.name).startsWith(q)||hbNorm(p.display_name).startsWith(q)?1:2)+(p.kind==='depo'?0:0.5);
return {p,score}}).filter(Boolean).sort((a,b)=>a.score-b.score||hbNorm(a.p.name).localeCompare(hbNorm(b.p.name),'ru')).map(x=>x.p)}
function hbBind(inp,box,list,onPick){
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
let hits=[],picked=inp.dataset.picked==='1';
const q=()=>picked?'':inp.value;
const draw=()=>{hits=hbFind(list,q());box.innerHTML=(hits.length?'<div class="hb-m" style="padding:2px 4px">'+(q().trim()?'Найдено: ':'Все участники: ')+hits.length+'</div>'+hits.map((p,i)=>'<button type="button" class="hb-hit" data-i="'+i+'"><b>'+esc(p.name)+'</b> <span class="hb-m">ИНН '+esc(p.inn)+' · '+esc(p.code)+(p.kind==='broker'?' · брокер':'')+'</span></button>').join(''):'<div class="hb-m" style="padding:6px 2px">В справочнике не найдено — введите ИНН и наименование в поля ниже.</div>');box.classList.add('open')};
const close=()=>{box.innerHTML='';box.classList.remove('open')};
inp.addEventListener('focus',()=>{if(picked)inp.select();draw()});
inp.addEventListener('input',()=>{picked=false;draw()});
inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();if(hits[0]){pick(hits[0])}}if(e.key==='Escape')close()});
const pick=p=>{onPick(p);picked=true;close()};
box.addEventListener('mousedown',e=>e.preventDefault());
box.addEventListener('click',e=>{const b=e.target.closest('.hb-hit');if(b)pick(hits[+b.dataset.i])});
inp.addEventListener('blur',()=>setTimeout(close,150));
const tg=inp.parentNode.querySelector('.hb-toggle');
if(tg)tg.addEventListener('click',()=>{if(box.classList.contains('open'))close();else{inp.focus();draw()}});
}
{
const inp=document.getElementById('hbq');
{const cur=({{.Handbook}}||[]).find(p=>p.inn===document.getElementById('recv_inn').value);if(inp&&cur){inp.value=cur.name+' · ИНН '+cur.inn;inp.dataset.picked='1'}}
if(inp)hbBind(inp,document.getElementById('hbres'),{{.Handbook}}||[],p=>{document.getElementById('recv_inn').value=p.inn;document.getElementById('recv_name').value=p.name;inp.value=p.name+' · ИНН '+p.inn})
}
</script>
{{end}}