d2024b44ca
- ручных отметок перед отправкой нет: автор виден по учётной записи, клиент/счёт/остаток/
бумага проверены сверкой с DEPO при сохранении; блокировка в QUIK — необязательно,
по умолчанию не требуется (модуль неторговых поручений сейчас не используется)
- «Удалить черновик» в карточке (POST /admin/manual/{id}/delete), только для черновиков
- количество не больше остатка на торговом разделе: проверка в форме (по выбранной бумаге)
и на сервере при сохранении (по счёту DEPO) — поручение с превышением не сохраняется
- отчёт MS010 с отказом: orderResponse.Rejected, OutgoingDeal.Rejected; шаг «Операция
завершена отказом» и бейдж — красные в карточках исходящего и входящего; онлайн-статус
«поручение 16 завершено отказом НРД»
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
164 lines
7.6 KiB
Go
164 lines
7.6 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func manualTestOrder() *ManualOrder {
|
|
return &ManualOrder{
|
|
Status: "draft", PaperNo: "15", PaperDate: "2026-09-17", ReceivedVia: "лично в офисе",
|
|
LastName: "Иванов", FirstName: "Иван", MiddleName: "Иванович", BirthDate: "1980-01-02",
|
|
DocType: "21", DocSeries: "0000", DocNumber: "000000", Account: "СЧЁТ-1",
|
|
RecvINN: "7702165310", RecvName: "Депозитарий", SettlePlace: "nrd",
|
|
Securities: []ManualSec{{ISIN: "RU0009029540", Qty: "10"}},
|
|
}
|
|
}
|
|
|
|
func TestManualSendBlockers(t *testing.T) {
|
|
rc, err := NewRuntimeConfig(t.TempDir() + "/setup.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
o := manualTestOrder()
|
|
// Ручных отметок нет, блокировка в QUIK по умолчанию не требуется: черновик готов сразу.
|
|
if b := manualSendBlockers(rc, o); len(b) != 0 {
|
|
t.Fatalf("готовый черновик: %v", b)
|
|
}
|
|
o.QuikMode = "manual"
|
|
if b := manualSendBlockers(rc, o); len(b) != 1 || !strings.Contains(b[0], "номер поручения") {
|
|
t.Fatalf("ручная блокировка без номера: %v", b)
|
|
}
|
|
o.QuikInstr = "123"
|
|
var b []string
|
|
if b = manualSendBlockers(rc, o); len(b) != 0 {
|
|
t.Fatalf("всё заполнено: %v", b)
|
|
}
|
|
o.QuikMode = "auto" // цикл QUIK не включён
|
|
if b = manualSendBlockers(rc, o); len(b) != 1 {
|
|
t.Fatalf("автоблокировка недоступна: %v", b)
|
|
}
|
|
o.QuikMode = "none"
|
|
o.Securities = []ManualSec{{ISIN: "US0378331005", Qty: "0"}}
|
|
if b = manualSendBlockers(rc, o); len(b) != 2 {
|
|
t.Fatalf("иностранная бумага и нулевое количество: %v", b)
|
|
}
|
|
o.Securities = []ManualSec{{ISIN: "RU0009029540", Qty: "1"}, {ISIN: "RU000A0JUR61", Qty: "1"}}
|
|
if b = manualSendBlockers(rc, o); len(b) != 1 || !strings.Contains(b[0], "одна бумага") {
|
|
t.Fatalf("одно поручение — одна бумага: %v", b)
|
|
}
|
|
}
|
|
|
|
func TestManualQtyAndClaim(t *testing.T) {
|
|
if q, ok := manualQty("1,5"); !ok || q.Fractional != "1.5" {
|
|
t.Fatalf("дробное: %+v", q)
|
|
}
|
|
if q, ok := manualQty("7"); !ok || q.Whole != 7 {
|
|
t.Fatalf("целое: %+v", q)
|
|
}
|
|
if _, ok := manualQty("-1"); ok {
|
|
t.Fatal("отрицательное")
|
|
}
|
|
rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json")
|
|
c, err := manualClaim(rc, manualTestOrder())
|
|
if err != nil || c.ClientAccount != "СЧЁТ-1" || c.Investor.BirthDate != "1980-01-02" || c.Securities[0].SecurityCode != "RU0009029540" || c.CostInfo.No == nil {
|
|
t.Fatalf("заявка: %+v %v", c, err)
|
|
}
|
|
}
|
|
|
|
// Страницы раздела: список, форма, карточка черновика, отметки, отказ, печать.
|
|
func TestManualPages(t *testing.T) {
|
|
saved := globalManual
|
|
globalManual = loadManualStore(t.TempDir() + "/manual.json")
|
|
defer func() { globalManual = saved }()
|
|
rc, err := NewRuntimeConfig(t.TempDir() + "/setup.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mux := http.NewServeMux()
|
|
registerManual(mux, rc, nil)
|
|
do := func(method, path string, form url.Values) *httptest.ResponseRecorder {
|
|
var req *http.Request
|
|
if form != nil {
|
|
req = httptest.NewRequest(method, path, strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
} else {
|
|
req = httptest.NewRequest(method, path, nil)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|
|
if rec := do("GET", "/admin/help/manual", nil); rec.Code != 200 || !strings.Contains(rec.Body.String(), "Порядок работы") {
|
|
t.Fatalf("инструкция: %d", rec.Code)
|
|
}
|
|
if rec := do("GET", "/admin/manual/new", nil); rec.Code != 200 || !strings.Contains(rec.Body.String(), "Новое поручение") {
|
|
t.Fatalf("форма: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
form := url.Values{"paper_no": {"15"}, "paper_date": {"2026-09-17"}, "last_name": {"Иванов"}, "first_name": {"Иван"},
|
|
"birth_date": {"1980-01-02"}, "doc_type": {"21"}, "doc_series": {"00 00"}, "doc_number": {"000000"}, "account": {"счёт-1"},
|
|
"recv_inn": {"7702165310"}, "sec_isin": {"ru0009029540", ""}, "sec_qty": {"10", ""}, "sec_code": {"", ""}, "sec_name": {"", ""}, "do": {"save"}}
|
|
rec := do("POST", "/admin/manual/save", form)
|
|
if rec.Code != http.StatusSeeOther || !strings.HasPrefix(rec.Header().Get("Location"), "/admin/manual/1") {
|
|
t.Fatalf("сохранение: %d %s", rec.Code, rec.Header().Get("Location"))
|
|
}
|
|
o, _ := globalManual.Get(1)
|
|
if o.DocSeries != "0000" || o.Account != "СЧЁТ-1" || len(o.Securities) != 1 || o.Securities[0].ISIN != "RU0009029540" {
|
|
t.Fatalf("данные: %+v", o)
|
|
}
|
|
if rec = do("GET", "/admin/manual/1", nil); rec.Code != 200 || !strings.Contains(rec.Body.String(), "Сверка пройдена, поручение готово") {
|
|
t.Fatalf("карточка: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
chk := url.Values{"quik_mode": {"manual"}, "quik_instr": {"77"}}
|
|
do("POST", "/admin/manual/1/checks", chk)
|
|
o, _ = globalManual.Get(1)
|
|
if len(manualSendBlockers(rc, o)) != 0 || o.QuikMode != "manual" {
|
|
t.Fatalf("блокировка вручную: %+v %v", o.QuikMode, manualSendBlockers(rc, o))
|
|
}
|
|
if rec = do("GET", "/admin/manual/1", nil); !strings.Contains(rec.Body.String(), "Сверка пройдена, поручение готово") {
|
|
t.Fatalf("готово к отправке: %s", rec.Body.String())
|
|
}
|
|
// Изменение данных сбрасывает отметки.
|
|
form.Set("id", "1")
|
|
form.Set("sec_qty", "11")
|
|
do("POST", "/admin/manual/save", form)
|
|
if o, _ = globalManual.Get(1); len(o.Checks) != 0 {
|
|
t.Fatalf("отметки должны сброситься: %+v", o.Checks)
|
|
}
|
|
// Черновик можно удалить; отклонённое — нет.
|
|
do("POST", "/admin/manual/save", url.Values{"paper_date": {"2026-09-17"}, "last_name": {"Пётр"}, "first_name": {"П"}, "birth_date": {"1980-01-02"},
|
|
"doc_type": {"21"}, "doc_series": {"0000"}, "doc_number": {"000001"}, "account": {"счёт-2"}, "recv_inn": {"7702165310"}, "sec_isin": {"ru0009029540"}, "sec_qty": {"1"}, "do": {"save"}})
|
|
if rec = do("POST", "/admin/manual/2/delete", nil); rec.Code != http.StatusSeeOther || !strings.Contains(rec.Header().Get("Location"), "/admin/manual?") {
|
|
t.Fatalf("удаление черновика: %d %s", rec.Code, rec.Header().Get("Location"))
|
|
}
|
|
if _, ok := globalManual.Get(2); ok {
|
|
t.Fatal("черновик не удалён")
|
|
}
|
|
do("POST", "/admin/manual/1/decline", url.Values{"reason": {"нет подписи клиента"}})
|
|
o, _ = globalManual.Get(1)
|
|
if o.Status != "declined" || o.DeclinedAt.Before(time.Now().Add(-time.Minute)) {
|
|
t.Fatalf("отказ: %+v", o)
|
|
}
|
|
for _, p := range []string{"/admin/manual", "/admin/manual/1", "/admin/manual/1/print"} {
|
|
if rec = do("GET", p, nil); rec.Code != 200 || !strings.Contains(rec.Body.String(), "нет подписи клиента") && p != "/admin/manual" {
|
|
t.Fatalf("%s: %d %s", p, rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOrgIdentity(t *testing.T) {
|
|
var s Settings
|
|
s.NSD.DeponentCode = "MC0000000001"
|
|
if orgCode(s) != "MC0000000001" {
|
|
t.Fatalf("код по депозитарному коду: %q", orgCode(s))
|
|
}
|
|
s.NSD.OrgCode, s.NSD.OrgINN, s.NSD.OrgName = "mc0000000002", "7700000000", "Депозитарий"
|
|
if orgCode(s) != "MC0000000002" || orgINN(s) != "7700000000" || transferringINN(s) != "7700000000" || orgName(s) != "Депозитарий" {
|
|
t.Fatalf("организация из настроек: %q %q %q", orgCode(s), orgINN(s), orgName(s))
|
|
}
|
|
}
|