Files
Bridge-and-Join-s/internal/lkgateway/org.go
T
fontvielle e774fc8989 Анкета участника M2M: ссылка в разделе НРД + предзаполнение по фирме
- В «Настройка → НРД» добавлена карточка «Регистрация участника M2M» со ссылкой
  на /admin/setup/participant-form (раньше страница была только по URL).
- participantFormPrefill теперь заполняет анкету по реквизитам фирмы, а не только
  из автоопределения DEPO: берёт ИНН/коды из настроек НРД и наименования
  (полное/сокращённое/отображаемое/англ.) из справочника участников НРД
  (по MC-коду, затем по ИНН); DisplayName/Short подставляются, если пусты.
- org.go: добавлен handbookByINN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-21 16:27:14 +03:00

181 lines
7.0 KiB
Go

package lkgateway
// org.go — наша организация (депозитарий) в M2M: код участника, ИНН, наименование.
// Задаётся при настройке: выбором себя из справочника участников НРД или вручную. От неё зависят
// ИНН передающего/зачисляющего депозитария в пакетах, код отправителя (канал ИШ) и код фирмы в QUIK.
import (
"encoding/json"
"net/http"
"regexp"
"strings"
)
var regexpMCCode = regexp.MustCompile(`^[A-Z]{2}[0-9A-Z]{10}$`)
// handbookParticipants — участники M2M из справочника НРД (без мест расчётов).
func handbookParticipants() []HandbookEntry {
raw, err := readHandbook()
if err != nil {
return nil
}
var hb struct {
Participants []HandbookEntry `json:"participants"`
}
if json.Unmarshal(raw, &hb) != nil {
return nil
}
var out []HandbookEntry
for _, p := range hb.Participants {
if p.Kind != "place" && p.INN != "" {
out = append(out, p)
}
}
return out
}
// handbookByINN — участник справочника по ИНН (первый не-"place").
func handbookByINN(inn string) (HandbookEntry, bool) {
inn = strings.TrimSpace(inn)
if inn == "" {
return HandbookEntry{}, false
}
for _, p := range handbookParticipants() {
if p.INN == inn {
return p, true
}
}
return HandbookEntry{}, false
}
// handbookByCode — участник по MC-коду.
func handbookByCode(code string) (HandbookEntry, bool) {
code = strings.ToUpper(strings.TrimSpace(code))
if code == "" {
return HandbookEntry{}, false
}
for _, p := range handbookParticipants() {
if strings.EqualFold(p.Code, code) {
return p, true
}
}
return HandbookEntry{}, false
}
// orgCode — код участника M2M нашей организации: из настроек, иначе депозитарный код в НРД.
func orgCode(s Settings) string {
return strings.ToUpper(strings.TrimSpace(pick(s.NSD.OrgCode, s.NSD.DeponentCode)))
}
// orgINN — ИНН нашей организации: из настроек, иначе по коду из справочника участников.
func orgINN(s Settings) string {
if inn := strings.TrimSpace(s.NSD.OrgINN); inn != "" {
return inn
}
if p, ok := handbookByCode(orgCode(s)); ok {
return p.INN
}
return ""
}
// orgName — наименование нашей организации.
func orgName(s Settings) string {
if n := strings.TrimSpace(s.NSD.OrgName); n != "" {
return n
}
if p, ok := handbookByCode(orgCode(s)); ok {
return p.Name
}
return ""
}
// saveOrg — POST /admin/setup/org: организация выбрана из справочника (hb=код) или введена вручную. В мастере
// та же форма несёт и депозитарные реквизиты в НРД (deponent_code…): на шаге одна кнопка «Сохранить».
func (h *setupHandlers) saveOrg(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().NSD
changedCode, bad := orgFromForm(r, &s)
if bad == "" && r.PostForm.Has("deponent_code") {
bad = nsdReqFromForm(r, &s)
}
if bad != "" {
setupFlash(w, r, bad)
return
}
if err := h.rc.UpdateNSD(s); err != nil {
setupFlash(w, r, "Организация: ошибка сохранения: "+err.Error())
return
}
msg := "Организация сохранена: " + pick(s.OrgName, "—") + ", ИНН " + pick(s.OrgINN, "—") + ", код " + pick(s.OrgCode, "—")
if r.PostForm.Has("deponent_code") {
msg += ". Реквизиты в НРД: код " + pick(s.DeponentCode, "—") + ", счёт " + pick(s.AccountID, "—") + ", раздел " + pick(s.SectionID, "—")
}
if changedCode {
msg += ". Код отправителя применится после перезапуска bj-server."
}
setupFlash(w, r, msg)
}
// orgFromForm — код, ИНН и наименование организации из формы (выбор из справочника или вручную).
// Возвращает, сменился ли код, и текст ошибки.
func orgFromForm(r *http.Request, s *NSDSettings) (bool, string) {
code := strings.ToUpper(strings.TrimSpace(r.FormValue("org_code")))
inn := strings.TrimSpace(r.FormValue("org_inn"))
name := strings.TrimSpace(r.FormValue("org_name"))
if hb := strings.TrimSpace(r.FormValue("hb")); hb != "" {
if p, ok := handbookByCode(hb); ok {
code, inn, name = strings.ToUpper(p.Code), p.INN, pick(p.FullName, p.Name)
}
}
if inn != "" && !manualINNRe.MatchString(inn) {
return false, "Организация: ИНН юридического лица — 10 цифр"
}
if code != "" && !regexpMCCode.MatchString(code) {
return false, "Организация: код участника — вида MC0000000000"
}
changedCode := code != s.OrgCode
s.OrgCode, s.OrgINN, s.OrgName = code, inn, name
if s.DeponentCode == "" {
s.DeponentCode = code
}
return changedCode, ""
}
var nsdReqRe = regexp.MustCompile(`^[A-Z0-9]{4,30}$`)
// saveNSDRequisites — POST /admin/setup/nsd-requisites: депозитарный код, счёт и раздел организации в НРД
// (место хранения бумаг в пакетах). В мастере — на шаге «Организация», рядом с кодом и ИНН.
func (h *setupHandlers) saveNSDRequisites(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().NSD
if bad := nsdReqFromForm(r, &s); bad != "" {
setupFlash(w, r, bad)
return
}
if err := h.rc.UpdateNSD(s); err != nil {
setupFlash(w, r, "Реквизиты НРД: ошибка сохранения: "+err.Error())
return
}
setupFlash(w, r, "Реквизиты НРД сохранены: код "+pick(s.DeponentCode, "—")+", счёт "+pick(s.AccountID, "—")+", раздел "+pick(s.SectionID, "—"))
}
// nsdReqFromForm — депозитарный код, счёт и раздел организации в НРД из формы; текст ошибки.
func nsdReqFromForm(r *http.Request, s *NSDSettings) string {
dep := strings.ToUpper(strings.TrimSpace(r.FormValue("deponent_code")))
acc := strings.ToUpper(strings.TrimSpace(r.FormValue("account_id")))
sec := strings.ToUpper(strings.TrimSpace(r.FormValue("section_id")))
for _, v := range []struct{ name, val string }{{"депозитарный код", dep}, {"депозитарный счёт", acc}, {"раздел счёта", sec}} {
if v.val != "" && !nsdReqRe.MatchString(v.val) {
return "Реквизиты НРД: " + v.name + " — латинские буквы и цифры, без пробелов"
}
}
s.DeponentCode, s.AccountID, s.SectionID = dep, acc, sec
return ""
}