Входящие M2M: частичный приём — каждая бумага решается сама по себе (НРД допускает: по каждой принятой свой черновик 16/1); переводы на ИИС не принимаем — отказ M2M07 по всем бумагам
- finishResult: итог confirm / reject / partial, без отказа всем пакетом; - autoDecisions и ручной ответ: без правила «целиком», квал-предупреждение — отказ только по своей бумаге; - признак ИИС из заголовка запроса (IIAAgreementDetails), у сохранённых раньше — по исходному XML; - форма оператора: без автоматического «отказ всем», подсказка про ИИС; тексты и тесты. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -54,6 +54,7 @@ var templateFuncs = template.FuncMap{
|
||||
"devOn": authTemplateFuncs["devOn"],
|
||||
"devLicensed": authTemplateFuncs["devLicensed"],
|
||||
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
|
||||
"isIIS": incomingIsIIS, // входящий перевод на ИИС
|
||||
"depoSubs": func() any { return depoWizardSubs }, // экраны шага 3 мастера
|
||||
"authOpen": authOpen,
|
||||
"internalDocs": func() bool { return internalDocsBuilt },
|
||||
|
||||
@@ -339,35 +339,31 @@ func finishSec(sr *depoSecResult) {
|
||||
}
|
||||
}
|
||||
|
||||
// finishResult — итог по переводу. Пакет принимается только целиком (решение 16.09.2026): если хотя
|
||||
// бы по одной бумаге отказ, отказ рекомендуется по всем бумагам с кодом первой отказной.
|
||||
// finishResult — итог по переводу. Частичный приём (решение 25.09.2026): каждая бумага решается сама
|
||||
// по себе — прошедшие проверки принимаются, остальным отказ со своим кодом. НРД так допускает: по
|
||||
// каждой принятой бумаге приходит свой черновик 16/1 и подаётся своё поручение. Переводы на ИИС не
|
||||
// принимаются совсем — это отдельная проверка по каждой бумаге (отказ всем).
|
||||
func finishResult(res *depoResult) {
|
||||
first := -1
|
||||
for i, s := range res.Secs {
|
||||
if s.Recommend != "confirm" {
|
||||
first = i
|
||||
break
|
||||
conf, rej, code, sec := 0, 0, "", ""
|
||||
for _, s := range res.Secs {
|
||||
if s.Recommend == "confirm" {
|
||||
conf++
|
||||
continue
|
||||
}
|
||||
rej++
|
||||
if code == "" {
|
||||
code, sec = s.RejectCode, s.Security
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(res.Secs) == 0:
|
||||
case first < 0:
|
||||
case rej == 0:
|
||||
res.Recommend, res.Summary = "confirm", "принять"
|
||||
default:
|
||||
code, sec := res.Secs[first].RejectCode, res.Secs[first].Security
|
||||
passed := 0
|
||||
for i := range res.Secs {
|
||||
if res.Secs[i].Recommend == "confirm" {
|
||||
passed++
|
||||
res.Secs[i].Recommend, res.Secs[i].RejectCode = "reject", code
|
||||
res.Secs[i].Checks = append(res.Secs[i].Checks, depoCheck{Name: "Пакет принимается только целиком", OK: false, Code: code,
|
||||
Detail: "сама бумага проверку прошла; отказ всем пакетом из-за " + sec + " (" + code + ")"})
|
||||
}
|
||||
}
|
||||
case conf == 0:
|
||||
res.Recommend, res.Summary = "reject", "отказ "+code
|
||||
if passed > 0 {
|
||||
res.Summary = fmt.Sprintf("отказ пакетом %s (из-за %s)", code, sec)
|
||||
}
|
||||
default:
|
||||
res.Recommend = "partial"
|
||||
res.Summary = fmt.Sprintf("частично: принять %d, отказ %d (первый — %s по %s)", conf, rej, code, sec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +414,10 @@ func runDepoCheck(it *IncomingTransfer) depoResult {
|
||||
for _, s := range it.Securities {
|
||||
isin := strings.ToUpper(strings.TrimSpace(s.ISIN))
|
||||
sr := depoSecResult{ReferenceID: s.ReferenceID, Security: pick(isin, s.SecurityCode)}
|
||||
if incomingIsIIS(it) { // решение 25.09.2026: входящие переводы на ИИС не принимаем совсем
|
||||
sr.Checks = append(sr.Checks, depoCheck{Name: "Перевод не на ИИС", OK: false, Code: "M2M07",
|
||||
Detail: "перевод на ИИС (в запросе договор ИИС) — входящие переводы на ИИС не принимаем, отказ по всем бумагам"})
|
||||
}
|
||||
ruOK := strings.HasPrefix(isin, "RU")
|
||||
sr.Checks = append(sr.Checks, depoCheck{Name: "Российский выпуск (RU-ISIN)", OK: ruOK, Code: "M2M01",
|
||||
Detail: pick3(ruOK, "допускается к переводам M2M", "не-RU ISIN — к M2M не допускается")})
|
||||
|
||||
@@ -5,47 +5,50 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Пакет принимается только целиком: отказ по одной бумаге — отказ по всем с тем же кодом.
|
||||
func TestFinishResultWholePackage(t *testing.T) {
|
||||
// Частичный приём (решение 25.09.2026): каждая бумага решается сама по себе.
|
||||
func TestFinishResultPartial(t *testing.T) {
|
||||
res := depoResult{Secs: []depoSecResult{
|
||||
{Security: "RU0009029540", Recommend: "confirm"},
|
||||
{Security: "RU0007661625", Recommend: "reject", RejectCode: "M2M05"},
|
||||
{Security: "RU000A0JUR61", Recommend: "confirm"},
|
||||
}}
|
||||
finishResult(&res)
|
||||
if res.Recommend != "reject" || !strings.Contains(res.Summary, "отказ пакетом M2M05") {
|
||||
if res.Recommend != "partial" || !strings.Contains(res.Summary, "принять 2, отказ 1") {
|
||||
t.Fatalf("итог: %s %q", res.Recommend, res.Summary)
|
||||
}
|
||||
for _, s := range res.Secs {
|
||||
if s.Recommend != "reject" || s.RejectCode != "M2M05" {
|
||||
t.Fatalf("бумага %s: %s %s", s.Security, s.Recommend, s.RejectCode)
|
||||
}
|
||||
if res.Secs[0].Recommend != "confirm" || res.Secs[2].Recommend != "confirm" || res.Secs[1].RejectCode != "M2M05" {
|
||||
t.Fatalf("бумаги решаются отдельно: %+v", res.Secs)
|
||||
}
|
||||
if last := res.Secs[0].Checks[len(res.Secs[0].Checks)-1]; last.OK || !strings.Contains(last.Detail, "RU0007661625") {
|
||||
t.Fatalf("пояснение отказа пакетом: %+v", last)
|
||||
}
|
||||
|
||||
ok := depoResult{Secs: []depoSecResult{{Recommend: "confirm"}, {Recommend: "confirm"}}}
|
||||
finishResult(&ok)
|
||||
if ok.Recommend != "confirm" {
|
||||
t.Fatalf("все бумаги прошли: %s", ok.Recommend)
|
||||
}
|
||||
no := depoResult{Secs: []depoSecResult{{Recommend: "reject", RejectCode: "M2M01"}, {Recommend: "reject", RejectCode: "M2M05"}}}
|
||||
finishResult(&no)
|
||||
if no.Recommend != "reject" || no.Summary != "отказ M2M01" {
|
||||
t.Fatalf("все отклонены: %s %q", no.Recommend, no.Summary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageDecisions(t *testing.T) {
|
||||
in := []SecurityDecision{{ReferenceID: "A", Confirm: true}, {ReferenceID: "B", RejectCode: "M2M08"}, {ReferenceID: "C", Confirm: true}}
|
||||
out := packageDecisions(in)
|
||||
for _, d := range out {
|
||||
if d.Confirm || d.RejectCode != "M2M08" {
|
||||
t.Fatalf("частичное согласие не должно уходить: %+v", out)
|
||||
// Автоответ: частичное согласие уходит как есть; перевод на ИИС — отказ M2M07 по всем бумагам.
|
||||
func TestAutoDecisionsPartialAndIIS(t *testing.T) {
|
||||
it := &IncomingTransfer{Securities: []IncomingSecurity{{ReferenceID: "A"}, {ReferenceID: "B"}}}
|
||||
r := depoResult{Secs: []depoSecResult{{ReferenceID: "A", Recommend: "confirm"}, {ReferenceID: "B", Recommend: "reject", RejectCode: "M2M05"}}}
|
||||
d, _ := autoDecisions(it, r)
|
||||
if !d[0].Confirm || d[1].Confirm || d[1].RejectCode != "M2M05" {
|
||||
t.Fatalf("частичное согласие: %+v", d)
|
||||
}
|
||||
it.IIS = true
|
||||
d, _ = autoDecisions(it, r)
|
||||
for _, x := range d {
|
||||
if x.Confirm || x.RejectCode != "M2M07" {
|
||||
t.Fatalf("ИИС не принимаем: %+v", d)
|
||||
}
|
||||
}
|
||||
if !in[0].Confirm {
|
||||
t.Fatal("исходный срез не должен меняться")
|
||||
}
|
||||
all := []SecurityDecision{{ReferenceID: "A", Confirm: true}, {ReferenceID: "B", Confirm: true}}
|
||||
if got := packageDecisions(all); !got[0].Confirm || !got[1].Confirm {
|
||||
t.Fatalf("согласие по всем: %+v", got)
|
||||
legacy := &IncomingTransfer{RawXML: "<x><IIAAgreementDetails/></x>"}
|
||||
if !incomingIsIIS(legacy) {
|
||||
t.Fatal("ИИС у сохранённого раньше перевода — по исходному XML")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ type IncomingTransfer struct {
|
||||
DocNumber string `json:"doc_number,omitempty"`
|
||||
ReceivingINN string `json:"receiving_inn,omitempty"` // принимающий депозитарий (мы)
|
||||
Securities []IncomingSecurity `json:"securities"`
|
||||
IIS bool `json:"iis,omitempty"` // перевод на ИИС (в запросе есть IIAAgreementDetails)
|
||||
RawXML string `json:"raw_xml"`
|
||||
DecisionXML string `json:"decision_xml,omitempty"` // наш ответ M2MTransferDecision (для выгрузки регулятору)
|
||||
Status string `json:"status"` // pending | answered
|
||||
@@ -135,6 +136,7 @@ func (s *IncomingStore) AddFromRequest(req *m2m.M2MTransferRequest, rawXML strin
|
||||
id, _ := newID()
|
||||
inv := req.Data.InvestorInformation
|
||||
it := &IncomingTransfer{
|
||||
IIS: req.Header.IIAAgreementDetails != nil,
|
||||
ID: id,
|
||||
GUID: guid,
|
||||
RequestCreated: req.Header.CreationTimestamp.Time,
|
||||
@@ -358,3 +360,8 @@ func newID() (string, error) {
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// incomingIsIIS — перевод на ИИС: по признаку из запроса, а у сохранённых раньше — по исходному XML.
|
||||
func incomingIsIIS(it *IncomingTransfer) bool {
|
||||
return it.IIS || strings.Contains(it.RawXML, "IIAAgreementDetails")
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func answerIncoming(ctx context.Context, deps incomingDeps, id string, decisions
|
||||
}
|
||||
nsd := cfg.NSD
|
||||
|
||||
decisions = packageDecisions(decisions)
|
||||
decisions = iisDecisions(it, decisions)
|
||||
|
||||
// Карта решений по ReferenceID.
|
||||
byRef := make(map[string]SecurityDecision, len(decisions))
|
||||
@@ -154,32 +154,15 @@ func answerIncoming(ctx context.Context, deps incomingDeps, id string, decisions
|
||||
// transferringINN — ИНН нашей организации (передающий депозитарий в запросе, принимающий — в решении).
|
||||
func transferringINN(s Settings) string { return orgINN(s) }
|
||||
|
||||
// packageDecisions — пакет принимается только целиком (решение 16.09.2026): если хотя бы по одной бумаге
|
||||
// отказ, отказываем по всем; принятым бумагам ставится код первой отказной.
|
||||
func packageDecisions(decisions []SecurityDecision) []SecurityDecision {
|
||||
code := ""
|
||||
for _, d := range decisions {
|
||||
if !d.Confirm {
|
||||
code = d.RejectCode
|
||||
break
|
||||
}
|
||||
}
|
||||
if code == "" {
|
||||
for _, d := range decisions {
|
||||
if !d.Confirm {
|
||||
code = "M2M05"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if code == "" {
|
||||
// iisDecisions — входящие переводы на ИИС не принимаем (решение 25.09.2026): отказ M2M07 по всем
|
||||
// бумагам, даже если оператор отметил «принять». Остальные переводы — по каждой бумаге отдельно.
|
||||
func iisDecisions(it *IncomingTransfer, decisions []SecurityDecision) []SecurityDecision {
|
||||
if it == nil || !incomingIsIIS(it) {
|
||||
return decisions
|
||||
}
|
||||
out := make([]SecurityDecision, len(decisions))
|
||||
for i, d := range decisions {
|
||||
if d.Confirm {
|
||||
d.Confirm, d.RejectCode = false, code
|
||||
}
|
||||
d.Confirm, d.RejectCode = false, "M2M07"
|
||||
out[i] = d
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -3,8 +3,9 @@ package lkgateway
|
||||
// Автоматический режим M2M (решение 16.09.2026): ручные шаги оператора выполняются без участия
|
||||
// человека, чтобы соблюдать сроки и убрать человеческий фактор.
|
||||
//
|
||||
// - Входящий перевод ждёт решения → свежая сверка с DEPO → решение по её рекомендации: пакет
|
||||
// принимается только целиком; предупреждение со Strict (бумага для квалифицированных инвесторов
|
||||
// - Входящий перевод ждёт решения → свежая сверка с DEPO → решение по её рекомендации: каждая
|
||||
// бумага решается сама по себе (частичный приём), переводы на ИИС — отказ M2M07 по всем бумагам;
|
||||
// предупреждение со Strict (бумага для квалифицированных инвесторов
|
||||
// при неполной квалификации клиента) — отказ M2M08. Без сверки ничего не принимается: если DEPO
|
||||
// или ИШ недоступны, bj повторяет каждые m2mAutoRetry и шлёт оповещение.
|
||||
// - После нашего согласия НРД присылает черновик 16/1 (MF010) → поручение подаётся автоматически.
|
||||
@@ -144,15 +145,19 @@ func incomingHasConfirmed(it *IncomingTransfer) bool {
|
||||
}
|
||||
|
||||
// autoDecisions — решение по результату сверки: рекомендация по каждой бумаге, Strict-предупреждение
|
||||
// даёт отказ M2M08, пакет только целиком. why — пояснение для карточки.
|
||||
// даёт отказ M2M08 по этой бумаге, остальные решаются сами по себе; перевод на ИИС — отказ всем. why — пояснение для карточки.
|
||||
func autoDecisions(it *IncomingTransfer, r depoResult) (decisions []SecurityDecision, why string) {
|
||||
bySec := map[string]depoSecResult{}
|
||||
strictSec := map[string]bool{} // бумаги для квал. инвесторов при неполной квалификации — отказ M2M08 именно по ним
|
||||
strict := ""
|
||||
for _, sr := range r.Secs {
|
||||
bySec[sr.ReferenceID] = sr
|
||||
for _, c := range sr.Checks {
|
||||
if c.Strict && c.Warn && strict == "" {
|
||||
strict = sr.Security
|
||||
if c.Strict && c.Warn {
|
||||
strictSec[sr.ReferenceID] = true
|
||||
if strict == "" {
|
||||
strict = sr.Security
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +167,7 @@ func autoDecisions(it *IncomingTransfer, r depoResult) (decisions []SecurityDeci
|
||||
switch {
|
||||
case !ok:
|
||||
d.RejectCode = "M2M05"
|
||||
case strict != "":
|
||||
case strictSec[sec.ReferenceID]:
|
||||
d.RejectCode = "M2M08"
|
||||
case sr.Recommend == "confirm":
|
||||
d.Confirm = true
|
||||
@@ -174,10 +179,10 @@ func autoDecisions(it *IncomingTransfer, r depoResult) (decisions []SecurityDeci
|
||||
}
|
||||
decisions = append(decisions, d)
|
||||
}
|
||||
decisions = packageDecisions(decisions)
|
||||
decisions = iisDecisions(it, decisions)
|
||||
why = r.Summary
|
||||
if strict != "" {
|
||||
why = "отказ M2M08: бумага " + strict + " для квалифицированных инвесторов, квалификация клиента неполная"
|
||||
why += "; отказ M2M08 по " + strict + ": бумага для квалифицированных инвесторов, квалификация клиента неполная"
|
||||
}
|
||||
return decisions, why
|
||||
}
|
||||
|
||||
@@ -16,25 +16,21 @@ func TestAutoDecisions(t *testing.T) {
|
||||
t.Fatalf("согласие: %+v", dec)
|
||||
}
|
||||
|
||||
// Отказ по одной — отказ всем пакетом с её кодом.
|
||||
r = depoResult{Summary: "отказ пакетом M2M05", Secs: []depoSecResult{{ReferenceID: "A", Recommend: "confirm"}, {ReferenceID: "B", Recommend: "reject", RejectCode: "M2M05"}}}
|
||||
// Отказ по одной — частичный приём: остальные принимаются (решение 25.09.2026).
|
||||
r = depoResult{Summary: "частично", Secs: []depoSecResult{{ReferenceID: "A", Recommend: "confirm"}, {ReferenceID: "B", Recommend: "reject", RejectCode: "M2M05"}}}
|
||||
dec, _ = autoDecisions(it, r)
|
||||
for _, d := range dec {
|
||||
if d.Confirm || d.RejectCode != "M2M05" {
|
||||
t.Fatalf("пакет целиком: %+v", dec)
|
||||
}
|
||||
if !dec[0].Confirm || dec[1].Confirm || dec[1].RejectCode != "M2M05" {
|
||||
t.Fatalf("частичный приём: %+v", dec)
|
||||
}
|
||||
|
||||
// Бумага для квал. инвесторов при неполной квалификации — отказ M2M08 всем.
|
||||
// Бумага для квал. инвесторов при неполной квалификации — отказ M2M08 только по ней.
|
||||
r = depoResult{Summary: "принять", Secs: []depoSecResult{
|
||||
{ReferenceID: "A", Security: "RU000A10F5K6", Recommend: "confirm", Checks: []depoCheck{{Name: "Бумага только для квал. инвесторов", OK: true, Warn: true, Strict: true, Code: "M2M08"}}},
|
||||
{ReferenceID: "B", Recommend: "confirm", Checks: []depoCheck{{Name: "Квал-инвестор (если бумага только для квал.)", OK: true, Warn: true, Code: "M2M08"}}},
|
||||
}}
|
||||
dec, why := autoDecisions(it, r)
|
||||
for _, d := range dec {
|
||||
if d.Confirm || d.RejectCode != "M2M08" {
|
||||
t.Fatalf("строгое предупреждение: %+v", dec)
|
||||
}
|
||||
if dec[0].Confirm || dec[0].RejectCode != "M2M08" || !dec[1].Confirm {
|
||||
t.Fatalf("строгое предупреждение — отказ только по этой бумаге: %+v", dec)
|
||||
}
|
||||
if !strings.Contains(why, "RU000A10F5K6") {
|
||||
t.Fatalf("пояснение: %q", why)
|
||||
@@ -49,9 +45,9 @@ func TestAutoDecisions(t *testing.T) {
|
||||
t.Fatalf("информационное предупреждение не должно отказывать: %+v", dec)
|
||||
}
|
||||
|
||||
// Бумага без результата сверки — не принимаем.
|
||||
// Бумага без результата сверки — не принимаем (остальные решаются сами по себе).
|
||||
r = depoResult{Secs: []depoSecResult{{ReferenceID: "A", Recommend: "confirm"}}}
|
||||
if dec, _ = autoDecisions(it, r); dec[0].Confirm || dec[1].Confirm {
|
||||
if dec, _ = autoDecisions(it, r); !dec[0].Confirm || dec[1].Confirm {
|
||||
t.Fatalf("без сверки по бумаге: %+v", dec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
.dc-rec{display:inline-block;padding:5px 14px;border-radius:999px;font-weight:700;font-size:14px}
|
||||
.dc-rec.confirm{background:var(--ok-weak,#e6f4ea);color:var(--ok,#12813f)}
|
||||
.dc-rec.reject{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)}
|
||||
.dc-rec.partial,.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<h2>Ход исполнения</h2>
|
||||
<ol>
|
||||
<li><b>Пакет отправлен в НРД.</b> Сервис MOEX МОСТ проверяет формат и присылает квитанцию. Если есть ошибка, поручение завершается отказом с кодом, а блокировка снимается.</li>
|
||||
<li><b>Решение принимающего депозитария.</b> Депозитарий соглашается или отказывает (код отказа виден в карточке). Пакет принимается только целиком.</li>
|
||||
<li><b>Решение принимающего депозитария.</b> Депозитарий соглашается или отказывает по каждой бумаге отдельно (код отказа виден в карточке): возможен частичный приём. Переводы на ИИС не принимаются.</li>
|
||||
<li><b>Черновик поручения 16.</b> После согласия НРД присылает черновик поручения на списание.</li>
|
||||
<li><b>Поручение 16 подано.</b> Нажмите «Подать поручение 16 по черновику». Если включён автоматический режим M2M, bj подаёт поручение сам.</li>
|
||||
<li><b>Отчёт НРД об исполнении.</b> Когда отчёт (MS010) получен, бумаги списаны. Кнопка «Документы НРД» скачивает архив с квитанциями и отчётом, в том числе в PDF.</li>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
.dc-rec{display:inline-block;padding:4px 12px;border-radius:999px;font-weight:700;font-size:13px}
|
||||
.dc-rec.confirm{background:var(--ok-weak,#e6f4ea);color:var(--ok,#12813f)}
|
||||
.dc-rec.reject{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)}
|
||||
.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
.dc-rec.mixed,.dc-rec.partial{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
</style>
|
||||
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||
<h2 style="margin:0">Сверка с DEPO — {{if devOn}}эмуляция ответа{{else}}рекомендация по ответу{{end}}</h2>
|
||||
@@ -141,7 +141,7 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted" style="font-size:12.5px;margin:10px 0 0">Пакет принимается только целиком: при отказе по одной бумаге отказ ставится всем бумагам с тем же кодом (так и будет отправлено).</p>
|
||||
<p class="muted" style="font-size:12.5px;margin:10px 0 0">Решение по каждой бумаге отдельно: принятые бумаги НРД переведёт (по каждой придёт свой черновик 16/1), по остальным уйдёт отказ с выбранным кодом.{{if and $.It (isIIS $.It)}} <b>Перевод на ИИС — такие мы не принимаем: отказ M2M07 по всем бумагам будет отправлен в любом случае.</b>{{end}}</p>
|
||||
<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap">
|
||||
<button type="submit" class="btn btn-ok">Отправить решение{{if devOn}} роботу{{end}}</button>
|
||||
<button type="button" class="btn" onclick="m2mSetAll('confirm')">Принять все</button>
|
||||
@@ -161,16 +161,6 @@
|
||||
document.querySelectorAll('input.dec').forEach(function(r){
|
||||
r.addEventListener('change', function(){
|
||||
toggle(r.getAttribute('data-ref'));
|
||||
// Пакет целиком: отказ по одной бумаге — отказ по всем с тем же кодом.
|
||||
if(r.value === 'reject' && r.checked){
|
||||
var src = document.querySelector('select[name="reject_'+r.getAttribute('data-ref')+'"]');
|
||||
document.querySelectorAll('input.dec[value="reject"]').forEach(function(o){
|
||||
if(o.checked) return;
|
||||
o.checked = true; toggle(o.getAttribute('data-ref'));
|
||||
var sel = document.querySelector('select[name="reject_'+o.getAttribute('data-ref')+'"]');
|
||||
if(sel && src) sel.value = src.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
window.m2mSetAll = function(val){
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
.dc-rec{display:inline-block;padding:4px 12px;border-radius:999px;font-weight:700;font-size:13px}
|
||||
.dc-rec.confirm{background:var(--ok-weak,#e6f4ea);color:var(--ok,#12813f)}
|
||||
.dc-rec.reject{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)}
|
||||
.dc-rec.partial,.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
.dc-rec.mixed{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)}
|
||||
</style>
|
||||
<div style="margin-top:18px;padding:12px 14px;border:1px solid var(--border);border-radius:12px;background:var(--card)">
|
||||
|
||||
@@ -313,7 +313,7 @@
|
||||
{{template "orgSection" .}}
|
||||
<div class="card" id="m2m-auto">
|
||||
<h2><span class="dot {{if .Settings.M2MAuto.Enabled}}ok{{else}}warn{{end}}"></span>Автоматический режим M2M</h2>
|
||||
<p class="muted">Без участия оператора, чтобы соблюдать сроки: <strong>ответ на входящие переводы</strong> по свежей сверке с DEPO (пакет принимается только целиком; бумага для квалифицированных инвесторов при неполной или непрочитанной квалификации клиента — отказ M2M08), <strong>подача поручения 16/1</strong>, как только НРД пришлёт черновик после нашего согласия, и <strong>подача поручения 16</strong> по исходящим после согласия контрагента. Если DEPO или ИШ недоступны — bj повторяет каждые 3 минуты и шлёт оповещение; без сверки ничего не принимается. Обрабатывается только то, что пришло не раньше чем за сутки до включения. Кнопки в карточках остаются для ручной работы.</p>
|
||||
<p class="muted">Без участия оператора, чтобы соблюдать сроки: <strong>ответ на входящие переводы</strong> по свежей сверке с DEPO (решение по каждой бумаге отдельно — возможен частичный приём; переводы на ИИС — отказ M2M07; бумага для квалифицированных инвесторов при неполной или непрочитанной квалификации клиента — отказ M2M08), <strong>подача поручения 16/1</strong>, как только НРД пришлёт черновик после нашего согласия, и <strong>подача поручения 16</strong> по исходящим после согласия контрагента. Если DEPO или ИШ недоступны — bj повторяет каждые 3 минуты и шлёт оповещение; без сверки ничего не принимается. Обрабатывается только то, что пришло не раньше чем за сутки до включения. Кнопки в карточках остаются для ручной работы.</p>
|
||||
<form method="post" action="/admin/setup/m2m-auto" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:10px">
|
||||
<input type="hidden" name="section" value="m2m-auto">
|
||||
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="enabled" value="1" {{if .Settings.M2MAuto.Enabled}}checked{{end}}> Автоматический режим включён</label>
|
||||
|
||||
Reference in New Issue
Block a user