Тестирование M2M: количество бумаг выбирается в предпросмотре — сверка DEPO и отправка берут его

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-16 15:56:03 +03:00
parent cfd6dfa1e8
commit f35c72c50a
3 changed files with 80 additions and 9 deletions
+26
View File
@@ -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)
}
}
+45 -8
View File
@@ -18,6 +18,7 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
@@ -373,10 +374,11 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
ISIN, Code, Name, Qty string
Bad bool
}
qtys := m2mQtyOverrides(r, *c)
var secs []pvSec
for _, s := range c.Secs {
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),
})
}
@@ -420,7 +422,7 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
}
var ss []depoOutSec
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)
}(),
@@ -562,7 +564,8 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
back("Сначала укажите код участника контрагента (кнопка «Контрагент»)")
return
}
guid, err := h.m2mInitiate(*cs, plan)
qtys := m2mQtyOverrides(r, *cs)
guid, err := h.m2mInitiate(*cs, plan, qtys)
m2mMu.Lock()
plan = m2mLoadPlan()
st := plan.States[id]
@@ -574,7 +577,7 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
} else {
st.Status = "in_progress"
st.GUID = guid
st.Note = "Отправлено контрагенту " + plan.CounterpartyCode + " (GUID " + guid + "). Ждём ответ."
st.Note = "Отправлено контрагенту " + plan.CounterpartyCode + " (GUID " + guid + ")" + m2mQtyNote(*cs, qtys) + ". Ждём ответ."
msg = "Кейс " + id + ": исходящий M2M отправлен (GUID " + guid + "). Ответ придёт во «Входящие»."
}
st.Updated = time.Now()
@@ -585,8 +588,41 @@ func registerM2MTest(mux *http.ServeMux, a *admin, h *setupHandlers) {
})
}
// m2mInitiate строит исходящий M2M по кейсу и отправляет через НРД контрагенту.
func (h *setupHandlers) m2mInitiate(c m2mCase, plan m2mPlan) (string, error) {
// m2mQtyOverrides — количество по бумагам, выбранное в предпросмотре (qty_<ISIN>); неверное значение
// игнорируется (остаётся количество из плана).
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 {
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))
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 {
qty = Quantity{Fractional: s.Qty}
qty = Quantity{Fractional: q}
}
secs = append(secs, ClaimSec{
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><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>{{.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>
{{end}}
</tbody>
</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}}
{{if .WeInitiate}}
@@ -117,6 +124,7 @@
{{if .WeInitiate}}
<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}}">
{{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>
</form>
<a href="/admin/m2m-testing" class="btn">Отмена</a>