DEPO: тестовый прогон исходящего (копия сделки на реального клиента, имитация решения контрагента, формы 16 и MS010) + список сделок для прогона
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -1053,3 +1053,74 @@ func depoSimulateIncoming(ctx context.Context, rc *RuntimeConfig, it *IncomingTr
|
||||
}
|
||||
return guid, nil
|
||||
}
|
||||
|
||||
|
||||
// depoSimulateOutgoing — копия исходящего перевода (бумаги и решение контрагента как в оригинале)
|
||||
// на реального клиента DEPO по паспорту: новый GUID, шапка OUT + строки + события полного сценария
|
||||
// списания (форма 16 и MS010 имитируются). В НРД ничего не отправляется — только трансферные таблицы DEPO.
|
||||
func depoSimulateOutgoing(ctx context.Context, rc *RuntimeConfig, svc *Service, srcGUID, ser, num string) (string, error) {
|
||||
if svc == nil {
|
||||
return "", fmt.Errorf("сервис сделок не запущен")
|
||||
}
|
||||
d, err := svc.Repo().GetByGUID(ctx, m2m.UUID(strings.ToLower(strings.TrimSpace(srcGUID))))
|
||||
if err != nil || d == nil || d.Request == nil {
|
||||
if d2, e2 := svc.Repo().GetByGUID(ctx, m2m.UUID(strings.ToUpper(strings.TrimSpace(srcGUID)))); e2 == nil && d2 != nil && d2.Request != nil {
|
||||
d = d2
|
||||
} else {
|
||||
return "", fmt.Errorf("исходящая сделка %s не найдена", shortGUID(srcGUID))
|
||||
}
|
||||
}
|
||||
db, err := depoEventsDB(rc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
serN := strings.ReplaceAll(strings.TrimSpace(ser), " ", "")
|
||||
numN := strings.TrimSpace(num)
|
||||
var name string
|
||||
if err := db.QueryRowContext(ctx, fsql.Ev11, sql.Named("s", serN), sql.Named("n", numN)).Scan(&name); err != nil {
|
||||
return "", fmt.Errorf("клиент с паспортом %s %s и открытым торговым счётом в DEPO не найден", ser, num)
|
||||
}
|
||||
req := d.Request
|
||||
guid := strings.ToUpper(fmt.Sprintf("%08x-%04x-4000-8000-%012x", time.Now().Unix()&0xFFFFFFFF, time.Now().UnixNano()&0xFFFF, time.Now().UnixNano()&0xFFFFFFFFFFFF))
|
||||
place := rc.Snapshot().NSD.SectionID
|
||||
var lines []depoLine
|
||||
for _, s := range req.Data.TransferredSecurities.Securities {
|
||||
lines = append(lines, depoLine{Ref: string(s.ReferenceID), ISIN: quikSecISIN(s), Code: string(s.SecurityCode), Amount: quikSecQty(s.Quantity), Place: place})
|
||||
}
|
||||
inn := string(req.Data.ReceivingDepository.INN)
|
||||
cp := handbookCodeByINN(inn)
|
||||
if !depoUpsertOrder(rc, guid, "OUT", cp, name, strings.TrimSpace(ser+" "+num), ser, num, lines, "ТЕСТ по образцу "+shortGUID(string(d.GUID))+": исходящий, получатель "+cp+" "+handbookNameByCode(cp)) {
|
||||
return "", fmt.Errorf("не удалось записать шапку в FTV_M2M")
|
||||
}
|
||||
now := time.Now()
|
||||
// решение контрагента по строкам: как в оригинале; без решения — считаем подтверждённым
|
||||
rejected := map[string]bool{}
|
||||
if d.Decision != nil {
|
||||
for _, sec := range d.Decision.Data.Securities {
|
||||
if sec.TransferDecision.Rejection != nil {
|
||||
rejected[string(sec.ReferenceID)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range req.Data.TransferredSecurities.Securities {
|
||||
ref := string(s.ReferenceID)
|
||||
depoEventAt(rc, now, guid, ref, "BJ_SENT", "", "")
|
||||
if rejected[ref] {
|
||||
var codes []string
|
||||
for _, sec := range d.Decision.Data.Securities {
|
||||
if string(sec.ReferenceID) == ref && sec.TransferDecision.Rejection != nil {
|
||||
codes = sec.TransferDecision.Rejection.Codes
|
||||
}
|
||||
}
|
||||
depoEventAt(rc, now.Add(time.Minute), guid, ref, "BJ_CP_REJECT", strings.Join(codes, ";"), rejectCodesLabel(codes))
|
||||
continue
|
||||
}
|
||||
depoEventAt(rc, now.Add(time.Minute), guid, ref, "BJ_CP_CONFIRM", "", "")
|
||||
// форма 16 по бумаге и её MS010 — имитация
|
||||
depoEventAt(rc, now.Add(2*time.Minute), guid, ref, "BJ_ORDER_SENT", "", "")
|
||||
depoEventAt(rc, now.Add(3*time.Minute), guid, ref, "BJ_ORDER_ACCEPTED", "", "")
|
||||
depoEventAt(rc, now.Add(3*time.Minute), guid, ref, "BJ_EXECUTED", "", "")
|
||||
}
|
||||
return guid, nil
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
|
||||
)
|
||||
|
||||
@@ -350,6 +351,51 @@ func registerOutgoing(mux *http.ServeMux, a *admin, deps outgoingDeps) {
|
||||
mux.HandleFunc("/admin/outgoing/", func(w http.ResponseWriter, r *http.Request) {
|
||||
p := strings.TrimPrefix(r.URL.Path, "/admin/outgoing/")
|
||||
switch {
|
||||
case strings.HasSuffix(p, "/depo-sim"):
|
||||
// тестовый прогон исходящего в DEPO с реальным клиентом (без отправки в НРД)
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
guid := strings.TrimSuffix(p, "/depo-sim")
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
defer cancel()
|
||||
newGUID, err := depoSimulateOutgoing(ctx, globalRC, deps.svc, guid, r.FormValue("ser"), r.FormValue("num"))
|
||||
back := r.Referer()
|
||||
if back == "" {
|
||||
back = "/admin/claims"
|
||||
}
|
||||
if err != nil {
|
||||
http.Redirect(w, r, back+"?flash="+urlQ("Прогон в DEPO: "+err.Error()), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, back+"?flash="+urlQ("Прогон в DEPO записан: новый GUID "+newGUID+", клиент по паспорту "+r.FormValue("ser")+" "+r.FormValue("num")), http.StatusSeeOther)
|
||||
case p == "depo-sim-list":
|
||||
// список исходящих сделок для прогона: GUID, контрагент, клиент, решение
|
||||
deals, _ := deps.svc.Repo().List(r.Context(), m2mcore.Filter{Limit: 500})
|
||||
type row struct{ GUID, CP, Name, Created, Decision string }
|
||||
var rows []row
|
||||
for _, d := range deals {
|
||||
if d == nil || d.Request == nil {
|
||||
continue
|
||||
}
|
||||
inv := d.Request.Data.InvestorInformation
|
||||
dec := "нет решения"
|
||||
if d.Decision != nil {
|
||||
var parts []string
|
||||
for _, sec := range d.Decision.Data.Securities {
|
||||
if sec.TransferDecision.Rejection != nil {
|
||||
parts = append(parts, string(sec.ReferenceID)+":reject "+strings.Join(sec.TransferDecision.Rejection.Codes, ","))
|
||||
} else {
|
||||
parts = append(parts, string(sec.ReferenceID)+":confirm")
|
||||
}
|
||||
}
|
||||
dec = strings.Join(parts, "; ")
|
||||
}
|
||||
rows = append(rows, row{string(d.GUID), handbookCodeByINN(string(d.Request.Data.ReceivingDepository.INN)), strings.TrimSpace(inv.LastName + " " + inv.FirstName), d.CreatedAt.Format("2006-01-02 15:04"), dec})
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(rows)
|
||||
case strings.HasSuffix(p, "/submit-order"):
|
||||
submitOutgoingOrder(w, r, deps, strings.TrimSuffix(p, "/submit-order"))
|
||||
case strings.HasSuffix(p, "/order.xml"):
|
||||
|
||||
Reference in New Issue
Block a user