Files
Bridge-and-Join-s/internal/lkgateway/web/templates/part_org.html
T
zuevav c1f77df340 Мастер, «Организация»: одна кнопка «Сохранить»; в строке справочника — своя запись
- Организация и депозитарные реквизиты в НРД сохраняются одной кнопкой внизу шага
  (поля реквизитов — в общей форме org-form; saveOrg сохраняет и их, если они
  пришли; в настройках формы прежние). Повтор «Из DEPO: … подставлено» в мастере
  убран — то же в зелёной плашке.
- «Найти себя в справочнике участников M2M» не пустая: по коду и ИНН из полей
  показывается своя запись и «✓ Есть в справочнике участников M2M»; нет в
  справочнике — предупреждение.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
2026-10-06 16:27:44 +03:00

84 lines
9.5 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.
{{/* «Наша организация» — одна и та же в «Настройка → НРД» и в мастере настройки (данные — SetupData). */}}
{{define "orgSection"}}
<div class="card" id="sec-org">
{{if not .InWizard}}<h2><span class="dot {{if and .OrgINN .OrgCode}}ok{{else}}err{{end}}"></span>Наша организация</h2>
<p class="muted">Депозитарий — участник MOEX МОСТ, от имени которого bj отправляет и принимает переводы: ИНН передающего и зачисляющего депозитария в пакетах{{if mod "quik-mnp"}}, код отправителя и код фирмы в QUIK{{end}}. Выберите себя из справочника участников НРД или введите данные вручную.</p>{{end}}
{{if and .OrgINN .OrgCode}}<p style="margin:8px 0 0"><b>{{if .OrgName}}{{.OrgName}}{{else}}—{{end}}</b> · ИНН <code>{{.OrgINN}}</code> · код <code>{{.OrgCode}}</code>{{if not .Settings.NSD.OrgINN}} <span class="muted">(ИНН взят из справочника по коду)</span>{{end}}</p>
{{else if not .InWizard}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
<form method="post" action="/admin/setup/org" id="org-form" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
<input type="hidden" name="section" value="sec-org">
{{if .InWizard}}<input type="hidden" name="return_to" value="/admin/wizard?step=4">{{end}}
<label>Найти себя в справочнике участников M2M</label>
<div class="hb-wrap"><input id="orgq" placeholder="Выберите из списка или введите ИНН, название, код" autocomplete="off"><button type="button" class="hb-toggle" title="Весь список">▾</button></div>
<div id="orgres" class="hb-res"></div>
<div id="orgmatch" class="hb-match" hidden></div>
<style>
.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}
.hb-match{font-size:13px;margin-top:-2px}.hb-match.ok{color:var(--ok)}.hb-match.warn{color:var(--warn)}
</style>
<script>
// 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+'" data-inn="'+esc(p.inn)+'" data-code="'+esc(p.code)+'"><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()});
// Enter выбирает только при единственном совпадении: иначе молча ушёл бы первый попавшийся.
inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();if(hits.length===1)pick(hits[0]);else if(hits.length>1){const m=box.querySelector('.hb-m');if(m)m.textContent='Найдено: '+hits.length+' — уточните запрос или выберите мышью'}}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)return;const p=list.find(x=>String(x.inn)===b.dataset.inn&&String(x.code)===b.dataset.code)||hits[+b.dataset.i];if(p)pick(p)});
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('orgq'),f=inp.form,mt=document.getElementById('orgmatch'),list={{.Participants}}||[];
// Организация уже известна (сохранена или найдена в DEPO) — показать её запись из справочника, а не пустую строку.
const mark=()=>{
const code=f.org_code.value.trim().toUpperCase(),inn=f.org_inn.value.trim();
if(!code&&!inn||!list.length){mt.hidden=true;return null}
const p=list.find(x=>String(x.code).toUpperCase()===code&&(!inn||String(x.inn)===inn))||(code?list.find(x=>String(x.code).toUpperCase()===code):null);
mt.hidden=false;
if(p){inp.value=p.name+' · ИНН '+p.inn+' · '+p.code;inp.dataset.picked='1';mt.className='hb-match ok';mt.textContent='✓ Есть в справочнике участников M2M: '+(p.full_name||p.name)+(inn&&String(p.inn)!==inn?' — но ИНН в справочнике '+p.inn+', проверьте':'');}
else{mt.className='hb-match warn';mt.textContent='⚠ Участника с кодом '+(code||'—')+' в справочнике M2M нет — проверьте код или выберите себя из списка.';}
return p};
// поля кода и ИНН — ниже этого скрипта: связать, когда страница загружена
const init=()=>{mark();
hbBind(inp,document.getElementById('orgres'),list,p=>{f.org_code.value=p.code;f.org_inn.value=p.inn;f.org_name.value=p.full_name||p.name;mark()});
f.org_code.addEventListener('change',mark);f.org_inn.addEventListener('change',mark)};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
}
</script>
{{if not .Participants}}<span class="muted" style="font-size:12px">Справочник участников ещё не получен — запросите его в «Диагностике» или введите данные вручную.</span>{{end}}
<label>Код участника</label>
<input type="text" name="org_code" value="{{if .Settings.NSD.OrgCode}}{{.Settings.NSD.OrgCode}}{{else if .Suggest}}{{.Suggest.OrgCode}}{{end}}" placeholder="MC0000000000" maxlength="12">
<label>ИНН</label>
<input type="text" name="org_inn" value="{{if .Settings.NSD.OrgINN}}{{.Settings.NSD.OrgINN}}{{else if .Suggest}}{{.Suggest.OrgINN}}{{end}}" maxlength="10" inputmode="numeric">
<label>Наименование</label>
<input type="text" name="org_name" value="{{if .Settings.NSD.OrgName}}{{.Settings.NSD.OrgName}}{{else if .Suggest}}{{.Suggest.OrgName}}{{end}}">
{{if and .Suggest (not .InWizard)}}{{if .Suggest.OrgName}}<span class="muted" style="font-size:12px">Из DEPO: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}} — подставлено в пустые поля.</span>{{end}}{{end}}
{{if not .InWizard}}<div><button type="submit" class="btn">Сохранить</button></div>{{end}}{{/* в мастере — одна кнопка внизу шага: организация и реквизиты НРД вместе */}}
</form>
</div>
{{end}}