diff --git a/internal/lkgateway/depoevents.go b/internal/lkgateway/depoevents.go index 3f77d9b..e19f64f 100644 --- a/internal/lkgateway/depoevents.go +++ b/internal/lkgateway/depoevents.go @@ -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 +} diff --git a/internal/lkgateway/outgoing_order.go b/internal/lkgateway/outgoing_order.go index 31e9ec7..0156655 100644 --- a/internal/lkgateway/outgoing_order.go +++ b/internal/lkgateway/outgoing_order.go @@ -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"):