Первичная настройка: реквизиты депозитария (счёт/код/раздел в НРД, организация) определяются из базы DEPO (Fancy) и подставляются в пустые поля карточек «ИШ НРД»/«Организация» и мастера — человек проверяет и сохраняет
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package lkgateway
|
||||
|
||||
// depo_detect.go — автоопределение реквизитов депозитария из базы DEPO (Fancy) при первичной
|
||||
// настройке: депозитарный счёт и разделы в НРД, код участника, карточка организации.
|
||||
// Человек проверяет предложенные значения в форме и сохраняет сам — в настройки ничего не
|
||||
// пишется автоматически.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||||
)
|
||||
|
||||
// DepoRequisites — что удалось определить в DEPO.
|
||||
type DepoRequisites struct {
|
||||
AccountID string // депозитарный счёт в НРД (HL…)
|
||||
DeponentCode string // код участника/депонента (MC…)
|
||||
SectionID string // раздел для M2M (36<код>F00)
|
||||
Sections []string // все открытые разделы счёта
|
||||
OrgCode string // = DeponentCode
|
||||
OrgINN string
|
||||
OrgName string
|
||||
OrgShort string
|
||||
OrgEng string
|
||||
Notes []string // что не нашли / на что обратить внимание
|
||||
At time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
depoSectionRe = regexp.MustCompile(`^(\d{2})([A-Z]{2}[0-9A-Z]{10})(F\d{2})$`)
|
||||
depoAccountRe = regexp.MustCompile(`^[A-Z]{2}[0-9A-Z]{10}$`)
|
||||
)
|
||||
|
||||
// lastDepoDetect — последний результат автоопределения (в памяти процесса): его показывают
|
||||
// карточки настроек как предложение, пока поля не сохранены.
|
||||
var lastDepoDetect struct {
|
||||
mu sync.Mutex
|
||||
r *DepoRequisites
|
||||
}
|
||||
|
||||
func depoDetectResult() *DepoRequisites {
|
||||
lastDepoDetect.mu.Lock()
|
||||
defer lastDepoDetect.mu.Unlock()
|
||||
return lastDepoDetect.r
|
||||
}
|
||||
|
||||
// depoDetectRequisites — читает DEPO и собирает реквизиты. Ничего не пишет.
|
||||
func depoDetectRequisites(ctx context.Context, fs FancyMSSQLSettings) (*DepoRequisites, error) {
|
||||
if strings.TrimSpace(fs.Server) == "" {
|
||||
return nil, fmt.Errorf("подключение к DEPO не настроено")
|
||||
}
|
||||
db, err := fancyOpen(fs, fs.DepoDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
res := &DepoRequisites{At: time.Now()}
|
||||
rows, err := db.QueryContext(ctx, fsql.Det01)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("счета в НРД (OD_CLIENTS): %w", err)
|
||||
}
|
||||
accounts := map[string]int{}
|
||||
codes := map[string]int{}
|
||||
sections := map[string]bool{}
|
||||
for rows.Next() {
|
||||
var number, name string
|
||||
var state int
|
||||
if rows.Scan(&number, &state, &name) != nil {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(strings.TrimSpace(number), "/")
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
}
|
||||
acc, sec := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[len(parts)-1])
|
||||
if depoAccountRe.MatchString(acc) {
|
||||
accounts[acc]++
|
||||
}
|
||||
if m := depoSectionRe.FindStringSubmatch(sec); m != nil {
|
||||
codes[m[2]]++
|
||||
if state == 0 {
|
||||
sections[sec] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if len(accounts) == 0 {
|
||||
res.Notes = append(res.Notes, "счета депозитария в НРД в DEPO не найдены (нет счетов у лица «НКО АО НРД», ИНН 7702165310) — реквизиты вводятся вручную из договора с НРД")
|
||||
return res, nil
|
||||
}
|
||||
res.AccountID = mostCommon(accounts)
|
||||
res.DeponentCode = mostCommon(codes)
|
||||
for s := range sections {
|
||||
res.Sections = append(res.Sections, s)
|
||||
}
|
||||
sort.Strings(res.Sections)
|
||||
// раздел для M2M — торговый «36<код>F00» (по договорённости с Fancy); иначе первый 36…, иначе первый открытый
|
||||
want := "36" + res.DeponentCode + "F00"
|
||||
switch {
|
||||
case sections[want]:
|
||||
res.SectionID = want
|
||||
default:
|
||||
for _, s := range res.Sections {
|
||||
if strings.HasPrefix(s, "36") {
|
||||
res.SectionID = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if res.SectionID == "" && len(res.Sections) > 0 {
|
||||
res.SectionID = res.Sections[0]
|
||||
}
|
||||
res.Notes = append(res.Notes, "раздела "+want+" нет среди открытых — проверьте выбранный раздел")
|
||||
}
|
||||
if len(accounts) > 1 {
|
||||
res.Notes = append(res.Notes, fmt.Sprintf("в DEPO несколько депозитарных счетов в НРД (%d) — выбран самый частый %s", len(accounts), res.AccountID))
|
||||
}
|
||||
// организация по коду участника
|
||||
if res.DeponentCode != "" {
|
||||
var name, inn, mnem, eng string
|
||||
err := db.QueryRowContext(ctx, fsql.Det02, sql.Named("code", res.DeponentCode)).Scan(&name, &inn, &mnem, &eng)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
res.Notes = append(res.Notes, "лицо с кодом "+res.DeponentCode+" в DEPO не найдено — ИНН и наименование организации введите вручную")
|
||||
case err != nil:
|
||||
res.Notes = append(res.Notes, "карточка организации: "+err.Error())
|
||||
default:
|
||||
res.OrgCode, res.OrgINN, res.OrgName, res.OrgShort, res.OrgEng = res.DeponentCode, strings.TrimSpace(inn), strings.TrimSpace(name), strings.TrimSpace(mnem), strings.TrimSpace(eng)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func mostCommon(m map[string]int) string {
|
||||
best, n := "", -1
|
||||
for k, v := range m {
|
||||
if v > n || (v == n && k < best) {
|
||||
best, n = k, v
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// Summary — короткая строка для сообщения.
|
||||
func (r *DepoRequisites) Summary() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
var b []string
|
||||
if r.AccountID != "" {
|
||||
b = append(b, "счёт "+r.AccountID)
|
||||
}
|
||||
if r.DeponentCode != "" {
|
||||
b = append(b, "код "+r.DeponentCode)
|
||||
}
|
||||
if r.SectionID != "" {
|
||||
b = append(b, "раздел "+r.SectionID)
|
||||
}
|
||||
if r.OrgName != "" {
|
||||
b = append(b, r.OrgName+" (ИНН "+r.OrgINN+")")
|
||||
}
|
||||
return strings.Join(b, ", ")
|
||||
}
|
||||
|
||||
// detectFancyRequisites — POST /admin/setup/fancy/detect: определить реквизиты в DEPO и показать
|
||||
// их как предложение в карточках «ИШ НРД» и «Организация» (и в мастере).
|
||||
func (h *setupHandlers) detectFancyRequisites(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
res, err := depoDetectRequisites(r.Context(), h.rc.Snapshot().Fancy)
|
||||
if err != nil {
|
||||
setupFlash(w, r, "DEPO: реквизиты не определены — "+err.Error())
|
||||
return
|
||||
}
|
||||
lastDepoDetect.mu.Lock()
|
||||
lastDepoDetect.r = res
|
||||
lastDepoDetect.mu.Unlock()
|
||||
msg := "Из DEPO определено: " + res.Summary() + ". Значения подставлены в пустые поля — проверьте и нажмите «Сохранить»."
|
||||
if res.AccountID == "" {
|
||||
msg = "DEPO: " + strings.Join(res.Notes, "; ")
|
||||
} else if len(res.Notes) > 0 {
|
||||
msg += " Замечания: " + strings.Join(res.Notes, "; ")
|
||||
}
|
||||
setupFlash(w, r, msg)
|
||||
}
|
||||
Reference in New Issue
Block a user