Справочник участников: поиск по ИНН/названию/коду вместо списка (ручной ввод, организация); ручной ввод — только НРД

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-17 20:02:42 +03:00
parent 634244ee28
commit a9dc14969b
5 changed files with 65 additions and 18 deletions
@@ -479,11 +479,38 @@
{{else}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
<input type="hidden" name="section" value="sec-org">
<label>Из справочника участников M2M</label>
<select name="hb" onchange="var o=this.options[this.selectedIndex];if(o.value){this.form.org_code.value=o.value;this.form.org_inn.value=o.dataset.inn;this.form.org_name.value=o.dataset.name}">
<option value="">— выбрать себя —</option>
{{$code := .OrgCode}}{{range .Participants}}<option value="{{.Code}}" data-inn="{{.INN}}" data-name="{{if .FullName}}{{.FullName}}{{else}}{{.Name}}{{end}}" {{if eq .Code $code}}selected{{end}}>{{.Name}} · ИНН {{.INN}} · {{.Code}}</option>{{end}}
</select>
<label>Найти себя в справочнике участников M2M</label>
<input id="orgq" placeholder="ИНН, часть названия или код участника" autocomplete="off">
<div id="orgres" class="hb-res"></div>
<style>
.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}
</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.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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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('orgq'),f=inp.form;
hbBind(inp,document.getElementById('orgres'),{{.Participants}}||[],p=>{f.org_code.value=p.code;f.org_inn.value=p.inn;f.org_name.value=p.full_name||p.name;inp.value=p.name+' · ИНН '+p.inn})
}
</script>
{{if not .Participants}}<span class="muted" style="font-size:12px">Справочник участников ещё не получен — запросите его в «Диагностике» или введите данные вручную.</span>{{end}}
<label>Код участника</label>
<input type="text" name="org_code" value="{{.Settings.NSD.OrgCode}}" placeholder="MC0000000000" maxlength="12">