Анкета участника: что не известно точно — уточнить у НРД

Источник наименований: карточка нашей фирмы в справочнике НРД, без неё —
DEPO (черновик). Анкета предупреждает и даёт готовый текст запроса в НРД,
если карточки нет, полное похоже на краткое, отличается от карточки или
английские неполные; при ответе M2M37 — подсказка запросить наименования.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-22 10:07:33 +03:00
parent 9a227228e5
commit c546773840
2 changed files with 84 additions and 5 deletions
+72 -5
View File
@@ -146,6 +146,59 @@ func participantFormValidate(d participantFormData) []string {
return errs return errs
} }
// pfAbbrRe — наименование начинается с сокращённой формы (ООО, АО…) — для полного это ошибка.
var pfAbbrRe = regexp.MustCompile(`^(ООО|ОАО|ЗАО|ПАО|НАО|АО|АКБ|КБ|НКО|ИК)[\s"«]`)
// pfAskNSD — текст запроса в НРД о точных наименованиях участника (ответ НРД — источник истины).
func pfAskNSD(code string) string {
if code == "" {
code = "наш код участника"
}
return "Просим сообщить точное наименование, под которым участник " + code +
" зарегистрирован в клиентской базе НРД: полное (FullName), сокращённое (ShortName), " +
"а также английские полное и сокращённое, если они есть."
}
// pfNameCheck — что в наименованиях надо уточнить у НРД. НРД сверяет их со своей клиентской
// базой (M2M37): точный источник — карточка нашей фирмы в справочнике НРД или ответ НРД;
// данные DEPO — только черновик.
func pfNameCheck(rc *RuntimeConfig, d participantFormData) []string {
var w []string
hb, ok := participantCard(rc, d.INN)
switch {
case ok && strings.TrimSpace(hb.FullName) != "":
x := participantFormData{FullName: hb.FullName}
pfNames(&x)
if x.FullName != d.FullName {
w = append(w, "Полное наименование отличается от карточки нашей фирмы в справочнике НРД: «"+x.FullName+"». НРД сверяет с ней — без причины не меняйте.")
}
default:
w = append(w, "Наименования взяты из DEPO, в справочнике НРД нашей карточки нет. НРД сверяет их со своей клиентской базой — уточните точные наименования у НРД и впишите как есть.")
}
if pfAbbrRe.MatchString(d.FullName) || (d.FullName != "" && d.FullName == d.ShortName) {
w = append(w, "Полное наименование похоже на сокращённое — нужно полное («Общество с ограниченной ответственностью …»), как в клиентской базе НРД.")
}
if (d.EngShort != "" || d.EngDisplay != "") && d.EngFull == "" {
w = append(w, "Английские: указано краткое без полного — уточните полное у НРД или оставьте английские пустыми.")
}
return w
}
// participantCard — карточка нашей фирмы в справочнике НРД: по MC-коду, затем по ИНН.
func participantCard(rc *RuntimeConfig, inn string) (HandbookEntry, bool) {
if rc == nil {
return HandbookEntry{}, false
}
s := rc.Snapshot()
if hb, ok := handbookByCode(orgCode(s)); ok {
return hb, true
}
if inn = strings.TrimSpace(pick(inn, orgINN(s))); inn != "" {
return handbookByINN(inn)
}
return HandbookEntry{}, false
}
// pfQuotes — кавычки как в клиентской базе НРД: прямые ". // pfQuotes — кавычки как в клиентской базе НРД: прямые ".
var pfQuotes = strings.NewReplacer("«", `"`, "»", `"`, "“", `"`, "”", `"`, "„", `"`) var pfQuotes = strings.NewReplacer("«", `"`, "»", `"`, "“", `"`, "”", `"`, "„", `"`)
@@ -376,7 +429,16 @@ func registerParticipantForm(mux *http.ServeMux, rc *RuntimeConfig, igwClient *i
} }
xmlPreview := participantFormXML(d) xmlPreview := participantFormXML(d)
participantFormRender(w, d, errs, action, flash, flashKind, xmlPreview, participantRegStatus(rc), igwClient != nil && igwChannel != "", isProdIGW(igwChannel)) reg := participantRegStatus(rc)
if ws := pfNameCheck(rc, d); len(ws) > 0 {
code := ""
if rc != nil {
code = orgCode(rc.Snapshot())
}
reg += `<div class="warn"><b>Уточните у НРД:</b><ul><li>` + strings.Join(escapeAll(ws), "</li><li>") +
`</li></ul>Запрос в НРД (можно скопировать):<pre>` + html.EscapeString(pfAskNSD(code)) + `</pre></div>`
}
participantFormRender(w, d, errs, action, flash, flashKind, xmlPreview, reg, igwClient != nil && igwChannel != "", isProdIGW(igwChannel))
}) })
} }
@@ -400,6 +462,9 @@ func participantRegStatus(rc *RuntimeConfig) string {
out.WriteString(`Ответ НРД пока не получен.`) out.WriteString(`Ответ НРД пока не получен.`)
case strings.EqualFold(reg.Status, "ERROR"): case strings.EqualFold(reg.Status, "ERROR"):
out.WriteString(`<b style="color:#b00">НРД отклонил</b> ` + reg.RespAt.Format("02.01 15:04") + `: ` + html.EscapeString(reg.RespText) + `. Исправьте указанное и отправьте снова.`) out.WriteString(`<b style="color:#b00">НРД отклонил</b> ` + reg.RespAt.Format("02.01 15:04") + `: ` + html.EscapeString(reg.RespText) + `. Исправьте указанное и отправьте снова.`)
if containsStr(reg.Codes, "M2M37") {
out.WriteString(` Наименование не совпало с клиентской базой НРД — запросите у НРД точные наименования (текст запроса ниже) и впишите их без изменений.`)
}
default: default:
out.WriteString(`<b style="color:#2a7a2a">НРД принял</b> ` + reg.RespAt.Format("02.01 15:04") + `. ` + html.EscapeString(reg.RespText)) out.WriteString(`<b style="color:#2a7a2a">НРД принял</b> ` + reg.RespAt.Format("02.01 15:04") + `. ` + html.EscapeString(reg.RespText))
} }
@@ -467,18 +532,20 @@ button.primary{background:#1763cc;color:#fff;border-color:#1763cc}
pre{background:#f6f8fa;border:1px solid #ddd;border-radius:8px;padding:12px;overflow:auto;white-space:pre-wrap} pre{background:#f6f8fa;border:1px solid #ddd;border-radius:8px;padding:12px;overflow:auto;white-space:pre-wrap}
.ok{background:#e7f6e7;border:1px solid #58a858;padding:8px 12px;border-radius:6px;margin:10px 0} .ok{background:#e7f6e7;border:1px solid #58a858;padding:8px 12px;border-radius:6px;margin:10px 0}
.bad{background:#fdecec;border:1px solid #d66;padding:8px 12px;border-radius:6px;margin:10px 0} .bad{background:#fdecec;border:1px solid #d66;padding:8px 12px;border-radius:6px;margin:10px 0}
.note{color:#555;font-size:13px}</style></head><body> .note{color:#555;font-size:13px}
.warn{background:#fff7e0;border:1px solid #e0b040;padding:8px 12px;border-radius:6px;margin:10px 0}.warn pre{margin:6px 0 0}</style></head><body>
<h1>Регистрация участника сервиса M2M (МОСТ)</h1> <h1>Регистрация участника сервиса M2M (МОСТ)</h1>
<p class="note">Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M. <p class="note">Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M.
Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. В боевом контуре анкета подаётся <b>через ИШ</b> Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. В боевом контуре анкета подаётся <b>через ИШ</b>
пакетом <code>#M2MPF</code> (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на пакетом <code>#M2MPF</code> (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на
<code>` + participantFormRecipient + `</code> оставлено запасным каналом. Поля предзаполнены из <code>` + participantFormRecipient + `</code> оставлено запасным каналом. Наименования должны точно совпадать с
автоопределения DEPO — проверьте юридические реквизиты.</p> клиентской базой НРД (иначе <b>M2M37</b>): bj берёт их из карточки нашей фирмы в справочнике НРД, а если её нет — из DEPO.
Чего нет или в чём bj не уверен — уточните у НРД. Кавычки — прямые <code>"</code>.</p>
` + regHTML + flashHTML + vErr + ` ` + regHTML + flashHTML + vErr + `
<form method="post"> <form method="post">
<fieldset><legend>Организация</legend> <fieldset><legend>Организация</legend>
` + inp("inn", "ИНН (10 цифр)", d.INN, "7700000000") + ` ` + inp("inn", "ИНН (10 цифр)", d.INN, "7700000000") + `
` + inp("full", "Полное наименование (по уставу)", d.FullName, "") + ` ` + inp("full", "Полное наименование (как в клиентской базе НРД)", d.FullName, "Общество с ограниченной ответственностью …") + `
<div class="row">` + inp("short", "Сокращённое", d.ShortName, "") + inp("display", "Отображаемое (DisplayName)", d.DisplayName, "") + `</div> <div class="row">` + inp("short", "Сокращённое", d.ShortName, "") + inp("display", "Отображаемое (DisplayName)", d.DisplayName, "") + `</div>
</fieldset> </fieldset>
<fieldset><legend>Английские наименования (опционально)</legend> <fieldset><legend>Английские наименования (опционально)</legend>
@@ -27,3 +27,15 @@ func TestParticipantFormNames(t *testing.T) {
t.Error("пустые английские наименования не должны попадать в анкету") t.Error("пустые английские наименования не должны попадать в анкету")
} }
} }
// Без карточки НРД и с кратким вместо полного — подсказка уточнить у НРД.
func TestParticipantNameCheck(t *testing.T) {
d := participantFormData{FullName: `ООО "ИК "Фонтвьель"`, ShortName: `ООО "ИК "Фонтвьель"`}
ws := pfNameCheck(nil, d)
if len(ws) != 2 || !strings.Contains(ws[0], "уточните") || !strings.Contains(ws[1], "сокращённое") {
t.Fatalf("подсказки: %q", ws)
}
if !strings.Contains(pfAskNSD("MC0413600000"), "MC0413600000") {
t.Error("в запросе нет кода участника")
}
}