Тестирование M2M: количество бумаг выбирается в предпросмотре — сверка DEPO и отправка берут его
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
package lkgateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestM2MQtyOverrides(t *testing.T) {
|
||||||
|
c := m2mCase{ID: "2", Secs: []m2mSec{sSBER("10"), {ISIN: "RU000A0JUR61", Code: "PAI", Qty: "5", Frac: true}}}
|
||||||
|
form := url.Values{"qty_RU0009029540": {"1"}, "qty_RU000A0JUR61": {"2,5"}}
|
||||||
|
r := httptest.NewRequest("POST", "/admin/m2m-testing/initiate", strings.NewReader(form.Encode()))
|
||||||
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
got := m2mQtyOverrides(r, c)
|
||||||
|
if got["RU0009029540"] != "1" || got["RU000A0JUR61"] != "2.5" {
|
||||||
|
t.Fatalf("количество: %v", got)
|
||||||
|
}
|
||||||
|
if note := m2mQtyNote(c, got); !strings.Contains(note, "SBER/03 × 1 (по плану 10)") {
|
||||||
|
t.Fatalf("отметка: %q", note)
|
||||||
|
}
|
||||||
|
bad := httptest.NewRequest("GET", "/?qty_RU0009029540=1.5&qty_RU000A0JUR61=-1", nil)
|
||||||
|
if got := m2mQtyOverrides(bad, c); len(got) != 0 {
|
||||||
|
t.Fatalf("дробное для акций и отрицательное — игнорировать: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -373,10 +374,11 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
|
|||||||
ISIN, Code, Name, Qty string
|
ISIN, Code, Name, Qty string
|
||||||
Bad bool
|
Bad bool
|
||||||
}
|
}
|
||||||
|
qtys := m2mQtyOverrides(r, *c)
|
||||||
var secs []pvSec
|
var secs []pvSec
|
||||||
for _, s := range c.Secs {
|
for _, s := range c.Secs {
|
||||||
secs = append(secs, pvSec{
|
secs = append(secs, pvSec{
|
||||||
ISIN: s.ISIN, Code: s.Code, Name: m2mSecName[s.ISIN], Qty: s.Qty,
|
ISIN: s.ISIN, Code: s.Code, Name: m2mSecName[s.ISIN], Qty: pickStr(qtys[s.ISIN], s.Qty),
|
||||||
Bad: m2mSecBad(*c, s),
|
Bad: m2mSecBad(*c, s),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -420,7 +422,7 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
|
|||||||
}
|
}
|
||||||
var ss []depoOutSec
|
var ss []depoOutSec
|
||||||
for _, s := range c.Secs {
|
for _, s := range c.Secs {
|
||||||
ss = append(ss, depoOutSec{ReferenceID: s.Code, ISIN: s.ISIN, Code: s.Code, Qty: s.Qty})
|
ss = append(ss, depoOutSec{ReferenceID: s.Code, ISIN: s.ISIN, Code: s.Code, Qty: pickStr(qtys[s.ISIN], s.Qty)})
|
||||||
}
|
}
|
||||||
return runDepoCheckOut(cl.Last, cl.First, cl.Middle, cl.DocSeries, cl.DocNumber, ss, nsd.AccountID, nsd.SectionID)
|
return runDepoCheckOut(cl.Last, cl.First, cl.Middle, cl.DocSeries, cl.DocNumber, ss, nsd.AccountID, nsd.SectionID)
|
||||||
}(),
|
}(),
|
||||||
@@ -562,7 +564,8 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
|
|||||||
back("Сначала укажите код участника контрагента (кнопка «Контрагент»)")
|
back("Сначала укажите код участника контрагента (кнопка «Контрагент»)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guid, err := h.m2mInitiate(*cs, plan)
|
qtys := m2mQtyOverrides(r, *cs)
|
||||||
|
guid, err := h.m2mInitiate(*cs, plan, qtys)
|
||||||
m2mMu.Lock()
|
m2mMu.Lock()
|
||||||
plan = m2mLoadPlan()
|
plan = m2mLoadPlan()
|
||||||
st := plan.States[id]
|
st := plan.States[id]
|
||||||
@@ -574,7 +577,7 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
|
|||||||
} else {
|
} else {
|
||||||
st.Status = "in_progress"
|
st.Status = "in_progress"
|
||||||
st.GUID = guid
|
st.GUID = guid
|
||||||
st.Note = "Отправлено контрагенту " + plan.CounterpartyCode + " (GUID " + guid + "). Ждём ответ."
|
st.Note = "Отправлено контрагенту " + plan.CounterpartyCode + " (GUID " + guid + ")" + m2mQtyNote(*cs, qtys) + ". Ждём ответ."
|
||||||
msg = "Кейс " + id + ": исходящий M2M отправлен (GUID " + guid + "). Ответ придёт во «Входящие»."
|
msg = "Кейс " + id + ": исходящий M2M отправлен (GUID " + guid + "). Ответ придёт во «Входящие»."
|
||||||
}
|
}
|
||||||
st.Updated = time.Now()
|
st.Updated = time.Now()
|
||||||
@@ -585,8 +588,41 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// m2mInitiate строит исходящий M2M по кейсу и отправляет через НРД контрагенту.
|
// m2mQtyOverrides — количество по бумагам, выбранное в предпросмотре (qty_<ISIN>); неверное значение
|
||||||
func (h *setupHandlers) m2mInitiate(c m2mCase, plan m2mPlan) (string, error) {
|
// игнорируется (остаётся количество из плана).
|
||||||
|
func m2mQtyOverrides(r *http.Request, c m2mCase) map[string]string {
|
||||||
|
out := map[string]string{}
|
||||||
|
for _, s := range c.Secs {
|
||||||
|
v := strings.ReplaceAll(strings.TrimSpace(r.FormValue("qty_"+s.ISIN)), ",", ".")
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err != nil || f <= 0 || (!s.Frac && f != float64(int64(f))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[s.ISIN] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// m2mQtyNote — «, количество: SBER/03 × 1» для отметки кейса, если количество меняли.
|
||||||
|
func m2mQtyNote(c m2mCase, qtys map[string]string) string {
|
||||||
|
var parts []string
|
||||||
|
for _, s := range c.Secs {
|
||||||
|
if q, ok := qtys[s.ISIN]; ok && q != s.Qty {
|
||||||
|
parts = append(parts, s.Code+" × "+q+" (по плану "+s.Qty+")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ", количество: " + strings.Join(parts, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// m2mInitiate строит исходящий M2M по кейсу и отправляет через НРД контрагенту. qtys — количество,
|
||||||
|
// выбранное в предпросмотре (ISIN → количество), иначе — из плана.
|
||||||
|
func (h *setupHandlers) m2mInitiate(c m2mCase, plan m2mPlan, qtys map[string]string) (string, error) {
|
||||||
if len(c.Secs) == 0 {
|
if len(c.Secs) == 0 {
|
||||||
return "", fmt.Errorf("в кейсе %s нет бумаг для отправки (это кейс сценария 2 — формируется вручную)", c.ID)
|
return "", fmt.Errorf("в кейсе %s нет бумаг для отправки (это кейс сценария 2 — формируется вручную)", c.ID)
|
||||||
}
|
}
|
||||||
@@ -596,9 +632,10 @@ func (h *setupHandlers) m2mInitiate(c m2mCase, plan m2mPlan) (string, error) {
|
|||||||
|
|
||||||
secs := make([]ClaimSec, 0, len(c.Secs))
|
secs := make([]ClaimSec, 0, len(c.Secs))
|
||||||
for _, s := range c.Secs {
|
for _, s := range c.Secs {
|
||||||
qty := Quantity{Whole: uint64(atoiSafe(s.Qty))}
|
q := pickStr(qtys[s.ISIN], s.Qty)
|
||||||
|
qty := Quantity{Whole: uint64(atoiSafe(q))}
|
||||||
if s.Frac {
|
if s.Frac {
|
||||||
qty = Quantity{Fractional: s.Qty}
|
qty = Quantity{Fractional: q}
|
||||||
}
|
}
|
||||||
secs = append(secs, ClaimSec{
|
secs = append(secs, ClaimSec{
|
||||||
SecurityCode: s.Code,
|
SecurityCode: s.Code,
|
||||||
|
|||||||
@@ -74,11 +74,18 @@
|
|||||||
<td>{{if .Bad}}<span class="pill pill-bad">🔴</span>{{else}}<span class="pill pill-ok">🟢</span>{{end}}</td>
|
<td>{{if .Bad}}<span class="pill pill-bad">🔴</span>{{else}}<span class="pill pill-ok">🟢</span>{{end}}</td>
|
||||||
<td><code>{{.ISIN}}</code>{{if ne .Code .ISIN}} · {{.Code}}{{end}}</td>
|
<td><code>{{.ISIN}}</code>{{if ne .Code .ISIN}} · {{.Code}}{{end}}</td>
|
||||||
<td>{{.Name}}{{if .Bad}} <span class="muted" style="font-size:12px;color:#b3261e">— неверная для этого кейса</span>{{end}}</td>
|
<td>{{.Name}}{{if .Bad}} <span class="muted" style="font-size:12px;color:#b3261e">— неверная для этого кейса</span>{{end}}</td>
|
||||||
<td>{{.Qty}}</td>
|
<td>{{if $.WeInitiate}}<input type="text" name="qty_{{.ISIN}}" value="{{.Qty}}" form="m2m-qty" inputmode="decimal" style="width:90px">{{else}}{{.Qty}}{{end}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{{if .WeInitiate}}
|
||||||
|
<form id="m2m-qty" method="get" action="/admin/m2m-testing/preview" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:8px">
|
||||||
|
<input type="hidden" name="id" value="{{.Case.ID}}">
|
||||||
|
<button type="submit" class="btn">↻ Пересчитать сверку с этим количеством</button>
|
||||||
|
<span class="muted" style="font-size:12px">Количество можно изменить (по плану — как в таблице кейсов); сверка DEPO и отправка берут то, что указано здесь после пересчёта.</span>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .WeInitiate}}
|
{{if .WeInitiate}}
|
||||||
@@ -117,6 +124,7 @@
|
|||||||
{{if .WeInitiate}}
|
{{if .WeInitiate}}
|
||||||
<form method="post" action="/admin/m2m-testing/initiate" onsubmit="return confirm('{{if .AlreadySent}}Отправить кейс {{.Case.ID}} ЗАНОВО? Уйдёт новая заявка с новым GUID (НРД примет как новую). Контрагент увидит ещё один перевод.{{else}}Отправить кейс {{.Case.ID}} контрагенту? Уйдёт реальный пакет в НРД (TEST3).{{end}}');">
|
<form method="post" action="/admin/m2m-testing/initiate" onsubmit="return confirm('{{if .AlreadySent}}Отправить кейс {{.Case.ID}} ЗАНОВО? Уйдёт новая заявка с новым GUID (НРД примет как новую). Контрагент увидит ещё один перевод.{{else}}Отправить кейс {{.Case.ID}} контрагенту? Уйдёт реальный пакет в НРД (TEST3).{{end}}');">
|
||||||
<input type="hidden" name="id" value="{{.Case.ID}}">
|
<input type="hidden" name="id" value="{{.Case.ID}}">
|
||||||
|
{{range .Secs}}<input type="hidden" name="qty_{{.ISIN}}" value="{{.Qty}}">{{end}}
|
||||||
<button type="submit" class="btn {{if .AlreadySent}}{{else}}btn-ok{{end}}">{{if .AlreadySent}}▶ Отправить заново (новый GUID){{else}}▶ Подтвердить и отправить в НРД{{end}}</button>
|
<button type="submit" class="btn {{if .AlreadySent}}{{else}}btn-ok{{end}}">{{if .AlreadySent}}▶ Отправить заново (новый GUID){{else}}▶ Подтвердить и отправить в НРД{{end}}</button>
|
||||||
</form>
|
</form>
|
||||||
<a href="/admin/m2m-testing" class="btn">Отмена</a>
|
<a href="/admin/m2m-testing" class="btn">Отмена</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user