diff --git a/internal/lkgateway/depotest.go b/internal/lkgateway/depotest.go index 45e8c35..d512e01 100644 --- a/internal/lkgateway/depotest.go +++ b/internal/lkgateway/depotest.go @@ -44,6 +44,7 @@ var depoTestOutcomes = []struct{ Code, Title string }{ type DepoTestLine struct { Ref, ISIN, Qty, Outcome, Code string Code2 string // код бумаги в НРД, ушедший в SEC_CODE + Gate string // проверка шлюза bj (как в бою): пусто — пройдена, иначе причина State, DOL, Comment string // результат джобы по строке FTV_M2M_LINES } @@ -63,6 +64,7 @@ type DepoTestRun struct { Status string // running | done | error Doc, HeaderState, HeaderComment string Errors int + GateMode string // strict — как в бою | off — без проверки } type depoTestStore struct { @@ -187,6 +189,7 @@ func depoTestExec(rc *RuntimeConfig, run *DepoTestRun, pace time.Duration, stopO } }) if state == "ERROR" && stopOnError { + globalDepoTests.update(id, func(r *DepoTestRun) { r.Status = "stopped" }) depoTestFinish(db, id, run.GUID) return } @@ -253,6 +256,10 @@ func depoTestExec(rc *RuntimeConfig, run *DepoTestRun, pace time.Duration, stopO } }) if st == "ERROR" && stopOnError { + globalDepoTests.update(id, func(r *DepoTestRun) { + r.Status = "stopped" + r.Steps = append(r.Steps, DepoTestStep{At: time.Now(), Title: "Сценарий остановлен на ошибке джобы (галочка «остановиться на первой ошибке»): оставшиеся шаги не записаны", State: "WARN"}) + }) depoTestFinish(db, id, run.GUID) return } @@ -418,6 +425,35 @@ func registerDepoTest(mux *http.ServeMux, a *admin) { back("Добавьте хотя бы одну бумагу") return } + // Проверка шлюза bj — та же сверка с DEPO, что перед настоящей отправкой/решением. + run.GateMode = "strict" + if r.FormValue("gate") == "off" { + run.GateMode = "off" + } + gate := depoTestGate(run) + var blocked []string + for i := range run.Lines { + l := &run.Lines[i] + g, ok := gate[l.Ref] + if !ok || g.ok { + continue + } + l.Gate = g.text + if run.GateMode != "strict" || l.Outcome == dtDecline { + continue + } + if dir == "in" { + // как автоматический режим: отказ с кодом, который дала сверка + l.Outcome, l.Code = dtDecline, pick(g.code, "M2M05") + } else { + blocked = append(blocked, l.ISIN+": "+g.text) + } + } + if len(blocked) > 0 { + back("В бою bj не отправил бы этот перевод — сверка списания с DEPO не пройдена: " + strings.Join(blocked, "; ") + + ". Уберите бумагу или выберите «без проверки шлюза», если нужно проверить именно джобу DEPO.") + return + } pace, _ := strconv.Atoi(r.FormValue("pace")) stop := r.FormValue("stop") == "1" globalDepoTests.mu.Lock() @@ -436,3 +472,46 @@ func registerDepoTest(mux *http.ServeMux, a *admin) { http.Redirect(w, r, "/admin/depo-test#run-"+run.ID, http.StatusSeeOther) }) } + +type depoGateLine struct { + ok bool + code, text string +} + +// depoTestGate — сверка с DEPO для строк сценария (runDepoCheck / runDepoCheckOut, как в бою). +func depoTestGate(run *DepoTestRun) map[string]depoGateLine { + out := map[string]depoGateLine{} + fio := strings.Fields(run.Client) + for len(fio) < 3 { + fio = append(fio, "") + } + var res depoResult + if run.Direction == "out" { + var secs []depoOutSec + for _, l := range run.Lines { + secs = append(secs, depoOutSec{ReferenceID: l.Ref, ISIN: l.ISIN, Code: l.ISIN, Qty: l.Qty}) + } + nsd := globalRC.Snapshot().NSD + res = runDepoCheckOut(fio[0], fio[1], strings.Join(fio[2:], " "), run.Ser, run.Num, secs, nsd.AccountID, nsd.SectionID) + } else { + it := &IncomingTransfer{GUID: run.GUID, SenderCode: run.CP, ReceivedAt: time.Now(), + InvestorLast: fio[0], InvestorFirst: fio[1], InvestorMiddle: strings.Join(fio[2:], " "), + DocType: "21", DocSeries: run.Ser, DocNumber: run.Num} + for _, l := range run.Lines { + it.Securities = append(it.Securities, IncomingSecurity{ReferenceID: l.Ref, ISIN: l.ISIN, SecurityCode: l.ISIN, Quantity: l.Qty}) + } + res = runDepoCheck(it) + } + for _, s := range res.Secs { + g := depoGateLine{ok: s.Recommend != "reject", code: s.RejectCode} + var bad []string + for _, c := range s.Checks { + if !c.OK { + bad = append(bad, strings.TrimSpace(c.Name+" "+c.Detail)) + } + } + g.text = strings.Join(bad, "; ") + out[s.ReferenceID] = g + } + return out +} diff --git a/internal/lkgateway/web/templates/admin_depo_test.html b/internal/lkgateway/web/templates/admin_depo_test.html index 50bad3a..9363bfe 100644 --- a/internal/lkgateway/web/templates/admin_depo_test.html +++ b/internal/lkgateway/web/templates/admin_depo_test.html @@ -12,7 +12,7 @@ .st.ERROR{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)} .st.TODO,.st.WARN,.st.running{background:var(--warn-weak,#fff4e0);color:var(--warn,#9a6b12)} .st.done{background:var(--ok-weak,#e6f4ea);color:var(--ok,#12813f)} - .st.error{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)} + .st.error,.st.stopped{background:var(--err-weak,#fbe9e7);color:var(--err,#b3261e)} table.dt{width:100%;border-collapse:collapse;font-size:13px} table.dt td,table.dt th{padding:5px 8px;border-bottom:1px solid var(--border);text-align:left;vertical-align:top} .emu{color:var(--muted);font-size:12px} @@ -41,6 +41,8 @@
+
+
@@ -62,11 +64,11 @@ {{range .Runs}}
-

{{if eq .Status "running"}}идёт{{else if eq .Status "done"}}завершён{{else}}ошибка{{end}} +

{{if eq .Status "running"}}идёт{{else if eq .Status "done"}}завершён{{else if eq .Status "stopped"}}остановлен{{else}}ошибка{{end}} {{if eq .Direction "out"}}↗ Списание{{else}}↘ Зачисление{{end}} · {{.Client}} · {{.CPName}} ({{.CP}})

-

Прогон {{.ID}} · {{.Created.Format "02.01.2006 15:04:05"}} · GUID {{.GUID}} · документ DEPO {{if .Doc}}№ {{.Doc}}{{else}}—{{end}} · шапка {{.HeaderState}}{{if .Errors}} · ошибок: {{.Errors}}{{end}}

+

Прогон {{.ID}} · {{.Created.Format "02.01.2006 15:04:05"}} · GUID {{.GUID}} · документ DEPO {{if .Doc}}№ {{.Doc}}{{else}}—{{end}} · шапка {{.HeaderState}} · проверка шлюза: {{if eq .GateMode "off"}}выключена{{else}}как в бою{{end}}{{if .Errors}} · ошибок: {{.Errors}}{{end}}

- {{range .Lines}}{{end}} + {{range .Lines}}{{end}}
БумагаКол-воСценарийСтрока DEPO
{{.ISIN}}{{if .Code2}} · {{.Code2}}{{end}}
{{.Ref}}
{{.Qty}}{{.Outcome}}{{if .Code}} {{.Code}}{{end}}{{if .State}}{{.State}} {{if .DOL}}№ {{.DOL}}{{end}} {{.Comment}}{{else}}—{{end}}
{{.ISIN}}{{if .Code2}} · {{.Code2}}{{end}}
{{.Ref}}
{{.Qty}}{{.Outcome}}{{if .Code}} {{.Code}}{{end}}{{if .Gate}}
шлюз: {{.Gate}}{{end}}
{{if .State}}{{.State}} {{if .DOL}}№ {{.DOL}}{{end}} {{.Comment}}{{else}}—{{end}}
Шаги ({{len .Steps}})
ВремяШагСобытие / строкаДжоба DEPO