a9dc14969b
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
415 lines
36 KiB
HTML
415 lines
36 KiB
HTML
{{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-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:300px;overflow:auto;border-radius:8px}
|
||
.hb-res:empty{display:none}
|
||
.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>
|
||
<input id="hbq" placeholder="ИНН, часть названия или код участника" autocomplete="off">
|
||
<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 name="recv_account" value="{{.O.RecvAccount}}" placeholder="для сведения"></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=>({'&':'&','<':'<','>':'>','"':'"'}[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.length<2)return[];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).slice(0,12).map(x=>x.p)}
|
||
function hbBind(inp,box,list,onPick){
|
||
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
||
let hits=[];
|
||
const draw=()=>{hits=hbFind(list,inp.value);box.innerHTML=inp.value.trim().length<2?'':(hits.length?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>')};
|
||
inp.addEventListener('input',draw);
|
||
inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();if(hits[0]){onPick(hits[0]);box.innerHTML=''}}if(e.key==='Escape')box.innerHTML=''});
|
||
box.addEventListener('click',e=>{const b=e.target.closest('.hb-hit');if(!b)return;onPick(hits[+b.dataset.i]);box.innerHTML=''});
|
||
}
|
||
{
|
||
const inp=document.getElementById('hbq');
|
||
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}}
|