feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР

- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk)
- трансферные таблицы FTV_M2M: создание при установке, события, справочник участников
- сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO
- боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК
- онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML)
- модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-15 19:41:12 +03:00
parent 59fc774f08
commit da12a9ec1c
132 changed files with 29969 additions and 1089 deletions
+91
View File
@@ -0,0 +1,91 @@
package lkgateway
// xmlfields.go — разбор сырого XML запроса/решения M2M в таблицу «все поля» с
// человекочитаемыми подписями. Используется и на карточке входящего, и в отчётах
// для регулятора (входящие и исходящие) — чтобы визуально были видны ВСЕ данные,
// которые есть в XML, а не только выбранные.
import (
"html"
"html/template"
"regexp"
"strings"
)
// xmlLeafRe матчит листовые элементы <ns:Tag ...>значение</ns:Tag>.
var xmlLeafRe = regexp.MustCompile(`<(?:[A-Za-z0-9]+:)?([A-Za-z0-9_]+)\b[^>]*>([^<>]+)</(?:[A-Za-z0-9]+:)?[A-Za-z0-9_]+>`)
// xmlFieldLabels — подписи полей M2M на русском.
var xmlFieldLabels = map[string]string{
"GUID": "GUID перевода",
"CreationTimestamp": "Дата формирования запроса",
"SenderCode": "Код отправителя (депозитарий)",
"ReceiverCode": "Код получателя (депозитарий)",
"Code": "Код депонента (учёт стоимости)",
"LastName": "Фамилия инвестора",
"FirstName": "Имя инвестора",
"MiddleName": "Отчество инвестора",
"DocumentType": "Тип документа (код)",
"DocumentSeries": "Серия документа",
"DocumentNumber": "Номер документа",
"ReferenceId": "Референс ценной бумаги",
"SecurityCode": "Код НРД ценной бумаги",
"ISIN": "ISIN",
"Whole": "Количество (целое)",
"Fractional": "Количество (дробное)",
"DeponentCode": "Код депонента (счёт депо)",
"AccountId": "Счёт депо",
"SectionId": "Раздел счёта депо",
"IsolationStatus": "Статус обособления",
"AgreementType": "Тип договора ИИС",
}
// renderXMLFieldsTable возвращает HTML-таблицу со всеми листовыми полями XML.
// INN дизамбигуируется по порядку появления в документе.
func renderXMLFieldsTable(raw string) string {
if strings.TrimSpace(raw) == "" {
return ""
}
ms := xmlLeafRe.FindAllStringSubmatch(raw, -1)
if len(ms) == 0 {
return ""
}
innSeq := 0
var b strings.Builder
b.WriteString(`<table class="xmlfields" style="width:100%;border-collapse:collapse;font-size:13px">`)
b.WriteString(`<tr><th style="border:1px solid #ccc;padding:5px 8px;background:#f0f0f0;text-align:left">Поле</th>`)
b.WriteString(`<th style="border:1px solid #ccc;padding:5px 8px;background:#f0f0f0;text-align:left">Значение</th>`)
b.WriteString(`<th style="border:1px solid #ccc;padding:5px 8px;background:#f0f0f0;text-align:left">Тег XML</th></tr>`)
for _, m := range ms {
tag, val := m[1], strings.TrimSpace(m[2])
if val == "" || tag == "IsM2M" {
continue
}
label := xmlFieldLabels[tag]
if tag == "INN" {
innSeq++
switch innSeq {
case 1:
label = "ИНН передающего депозитария"
case 2:
label = "ИНН принимающего депозитария"
default:
label = "ИНН места расчётов (НРД/СПБ)"
}
}
if label == "" {
label = tag
}
b.WriteString(`<tr><td style="border:1px solid #ccc;padding:5px 8px">` + html.EscapeString(label) +
`</td><td style="border:1px solid #ccc;padding:5px 8px"><strong>` + html.EscapeString(val) +
`</strong></td><td style="border:1px solid #ccc;padding:5px 8px;color:#888;font-family:monospace;font-size:11px">` +
html.EscapeString(tag) + `</td></tr>`)
}
b.WriteString(`</table>`)
return b.String()
}
// xmlFieldsHTML — обёртка для шаблона (template.HTML, чтобы не экранировалось).
func xmlFieldsHTML(raw string) template.HTML {
return template.HTML(renderXMLFieldsTable(raw))
}