DEPO: события по ответу программиста bj2depo — без COMMENT/STATE, параметры в PARAMETERS, время события из bj

COMMENT и STATE в трансферных таблицах заполняет только bj2depo: bj их больше не пишет (шапка,
события, BJ_PING). Параметры событий — в PARAMETERS (CONTR_D_ID поручения НРД, номер поручения МНП,
текст причины отказа), EVENT_DATE — фактическое время события в bj. Событий от bj2depo нет: bj не
читает и не помечает DEPO_*, номер документа берёт из FTV_M2M.DOC. Страница «bj ↔ bj2depo» обновлена:
поля, правила, FTV_M2M_PROCESS_WIRING по BJ_ORDER_ACCEPTED/BJ_EXECUTED, примеры SQL, закрытые и
оставшиеся вопросы.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-17 12:54:50 +03:00
parent 28c0ed7349
commit 059206348f
2 changed files with 120 additions and 155 deletions
+68 -105
View File
@@ -2,11 +2,12 @@ package lkgateway
// Обмен с DEPO (Fancy) через трансферные таблицы в нашей копии базы
// (BJ_DEPO_DATA): FTV_M2M / FTV_M2M_LINES / FTV_M2M_EVENTS / FTV_M2M_HANDBOOK.
// Согласовано с программистом Fancy 09–10.09.2026 (страница /admin/help/fancy-events):
// - bj пишет поручения и события BJ_*; джоба bj2depo создаёт сущности DEPO и
// пишет в ответ только DEPO_ORDER_CREATED / DEPO_ERROR;
// - справочник участников — TRUNCATE + полная перезапись при каждом получении;
// - STATE: TODO при записи → SUCCESS/ERROR после обработки получателем.
// Согласовано с программистом Fancy (страница /admin/help/fancy-events), уточнено 17.09.2026:
// - bj — мастер: пишет поручения и события BJ_*; джоба bj2depo создаёт сущности DEPO, событий от
// себя не пишет — результат bj читает в FTV_M2M.DOC и в STATE/COMMENT своих событий;
// - COMMENT и STATE заполняет только bj2depo (STATE по умолчанию TODO); параметры события bj пишет
// в PARAMETERS; EVENT_DATE — фактическое время события в bj;
// - справочник участников — TRUNCATE + полная перезапись при каждом получении.
import (
"context"
@@ -124,13 +125,15 @@ func depoTableExists(ctx context.Context, db *sql.DB, name string) bool {
var depoTablesWarned time.Time
// depoEvent — одна строка FTV_M2M_EVENTS (идемпотентно по GUID+REFERENCE+EVENT_TYPE), дата = сейчас.
func depoEvent(rc *RuntimeConfig, guid, ref, typ, codes, comment string) {
depoEventAt(rc, time.Time{}, guid, ref, typ, codes, comment)
// depoEvent — одна строка FTV_M2M_EVENTS (идемпотентно по GUID+REFERENCE+EVENT_TYPE), время события — сейчас.
// params — параметры события для bj2depo (PARAMETERS): номер поручения НРД, номер поручения МНП, текст причины отказа.
func depoEvent(rc *RuntimeConfig, guid, ref, typ, codes, params string) {
depoEventAt(rc, time.Now(), guid, ref, typ, codes, params)
}
// depoEventAt — то же с явной датой события (для выгрузки истории).
func depoEventAt(rc *RuntimeConfig, at time.Time, guid, ref, typ, codes, comment string) {
// depoEventAt — то же с явным временем события (выгрузка истории). COMMENT и STATE не пишем:
// их заполняет bj2depo (STATE по умолчанию TODO).
func depoEventAt(rc *RuntimeConfig, at time.Time, guid, ref, typ, codes, params string) {
guid = strings.ToUpper(strings.TrimSpace(guid))
if !uuidRe.MatchString(guid) {
return // тестовые/служебные идентификаторы в DEPO не отдаём
@@ -149,8 +152,11 @@ func depoEventAt(rc *RuntimeConfig, at time.Time, guid, ref, typ, codes, comment
}
return
}
if len([]rune(comment)) > 4000 {
comment = string([]rune(comment)[:4000])
if at.IsZero() {
at = time.Now()
}
if len([]rune(params)) > 4000 {
params = string([]rune(params)[:4000])
}
var n int
_ = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dbo.FTV_M2M_EVENTS WHERE GUID=@g AND ISNULL(REFERENCE,'')=@r AND EVENT_TYPE=@t",
@@ -158,17 +164,18 @@ func depoEventAt(rc *RuntimeConfig, at time.Time, guid, ref, typ, codes, comment
if n > 0 {
return
}
// Параметр даты — типизированный NULL: при нетипизированном nil драйвер шлёт nvarchar(1),
// ISNULL берёт тип первого аргумента и GETDATE() обрезается до одного символа.
evDate := sql.NullTime{Time: at, Valid: !at.IsZero()}
_, err = db.ExecContext(ctx, `INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, STATE, EVENT_TYPE, EVENT_DATE, M2M_REJECT_CODE, COMMENT)
VALUES (@g, @r, 'TODO', @t, COALESCE(@at, GETDATE()), @c, @cm)`,
sql.Named("g", guid), sql.Named("r", nullStr(ref)), sql.Named("t", typ), sql.Named("at", evDate), sql.Named("c", nullStr(codes)), sql.Named("cm", nullStr(comment)))
q := `INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, EVENT_TYPE, EVENT_DATE, M2M_REJECT_CODE) VALUES (@g, @r, @t, @at, @c)`
if depoHasColumn(ctx, db, "FTV_M2M_EVENTS", "PARAMETERS") {
q = `INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, EVENT_TYPE, EVENT_DATE, M2M_REJECT_CODE, PARAMETERS) VALUES (@g, @r, @t, @at, @c, @p)`
}
_, err = db.ExecContext(ctx, q,
sql.Named("g", guid), sql.Named("r", nullStr(ref)), sql.Named("t", typ), sql.Named("at", at.In(time.FixedZone("МСК", 3*3600)).Format("2006-01-02T15:04:05.000")),
sql.Named("c", nullStr(codes)), sql.Named("p", nullStr(params)))
if err != nil {
log.Printf("depo-events: %s %s/%s: %v", typ, shortGUID(guid), ref, err)
return
}
log.Printf("depo-events: %s %s %s %s", typ, shortGUID(guid), ref, codes)
log.Printf("depo-events: %s %s %s %s %s", typ, shortGUID(guid), ref, codes, params)
}
type depoLine struct {
@@ -223,9 +230,10 @@ func depoUpsertOrder(rc *RuntimeConfig, guid, direction, cpCode, deponentName, d
log.Printf("depo-events: FTV_M2M %s: %v", shortGUID(guid), err)
return false
}
head := `INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, COMMENT) VALUES (@g, @d, @cp, @nm, @doc, @cm)`
// COMMENT не пишем — его заполняет bj2depo (17.09.2026); comment остаётся только для журнала bj.
head := `INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC) VALUES (@g, @d, @cp, @nm, @doc)`
if hasDeponentID {
head = `INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, DEPONENT_ID, COMMENT) VALUES (@g, @d, @cp, @nm, @doc, @did, @cm)`
head = `INSERT INTO dbo.FTV_M2M (GUID, DIRECTION, CONTRAGENT_M2M_CODE, DEPONENT_NAME, DEPONENT_DOC, DEPONENT_ID) VALUES (@g, @d, @cp, @nm, @doc, @did)`
}
if _, err := tx.ExecContext(ctx, head,
sql.Named("g", guid), sql.Named("d", direction), sql.Named("cp", nullStr(cpCode)), sql.Named("nm", nullStr(deponentName)), sql.Named("doc", nullStr(deponentDoc)),
@@ -310,7 +318,7 @@ func depoOnIncomingReceived(rc *RuntimeConfig, it *IncomingTransfer) {
name := strings.TrimSpace(strings.Join([]string{it.InvestorLast, it.InvestorFirst, it.InvestorMiddle}, " "))
doc := strings.TrimSpace(it.DocSeries + " " + it.DocNumber)
if depoUpsertOrder(rc, it.GUID, "IN", it.SenderCode, name, doc, it.DocSeries, it.DocNumber, lines, "входящий перевод M2M от "+it.SenderCode) {
depoEvent(rc, it.GUID, "", "BJ_RECEIVED", "", "входящий перевод получен, ждёт решения")
depoEvent(rc, it.GUID, "", "BJ_RECEIVED", "", "")
}
}
@@ -335,7 +343,7 @@ func depoOnOutgoingSent(rc *RuntimeConfig, req *m2m.M2MTransferRequest) {
name := strings.TrimSpace(strings.Join([]string{inv.LastName, inv.FirstName, inv.MiddleName}, " "))
guid := string(req.Header.GUID)
if depoUpsertOrder(rc, guid, "OUT", cp, name, strings.TrimSpace(ser+" "+num), ser, num, lines, "исходящий перевод M2M, получатель ИНН "+inn) {
depoEvent(rc, guid, "", "BJ_SENT", "", "запрос на перевод отправлен в НРД")
depoEvent(rc, guid, "", "BJ_SENT", "", "")
}
}
@@ -346,9 +354,9 @@ func depoOnIncomingDecision(rc *RuntimeConfig, it *IncomingTransfer, decisions [
}
for _, d := range decisions {
if d.Confirm {
depoEvent(rc, it.GUID, d.ReferenceID, "BJ_DECISION_CONFIRM", "", "приём бумаги подтверждён контрагенту")
depoEvent(rc, it.GUID, d.ReferenceID, "BJ_DECISION_CONFIRM", "", "")
} else {
depoEvent(rc, it.GUID, d.ReferenceID, "BJ_DECISION_REJECT", d.RejectCode, "отказ контрагенту: "+m2mCodeLabel(d.RejectCode))
depoEvent(rc, it.GUID, d.ReferenceID, "BJ_DECISION_REJECT", d.RejectCode, m2mCodeLabel(d.RejectCode))
}
}
}
@@ -363,33 +371,33 @@ func depoOnCounterpartyDecision(rc *RuntimeConfig, decision *m2m.M2MTransferDeci
ref := string(sec.ReferenceID)
if sec.TransferDecision.Rejection != nil {
codes := strings.Join(sec.TransferDecision.Rejection.Codes, ";")
depoEvent(rc, guid, ref, "BJ_CP_REJECT", codes, "отказ принимающего депозитария: "+rejectCodesLabel(sec.TransferDecision.Rejection.Codes))
depoEvent(rc, guid, ref, "BJ_CP_REJECT", codes, rejectCodesLabel(sec.TransferDecision.Rejection.Codes))
} else if sec.TransferDecision.Confirmation != nil {
depoEvent(rc, guid, ref, "BJ_CP_CONFIRM", "", "принимающий депозитарий подтвердил")
depoEvent(rc, guid, ref, "BJ_CP_CONFIRM", "", "")
}
}
}
func depoOnServiceError(rc *RuntimeConfig, guid, codes, reason string) {
depoEvent(rc, guid, "", "BJ_SERVICE_ERROR", strings.ReplaceAll(strings.TrimSpace(codes), " ", ";"), "отклонено сервисом MOEX МОСТ: "+reason)
depoEvent(rc, guid, "", "BJ_SERVICE_ERROR", strings.ReplaceAll(strings.TrimSpace(codes), " ", ";"), reason)
}
func depoOnOrderSent(rc *RuntimeConfig, guid string, contrDIDs []string, form string) {
depoEvent(rc, guid, "", "BJ_ORDER_SENT", "", "подано депо-поручение "+form+" в НРД, CONTR_D_ID "+strings.Join(contrDIDs, ", "))
depoEvent(rc, guid, "", "BJ_ORDER_SENT", "", strings.Join(contrDIDs, ";"))
}
// результат по депо-поручению: accepted | rejected | late | expired
func depoOnOrderResult(rc *RuntimeConfig, guid, kind, text string) {
switch kind {
case "accepted":
depoEvent(rc, guid, "", "BJ_ORDER_ACCEPTED", "", "НРД принял депо-поручение (отчёт MS010)")
depoEvent(rc, guid, "", "BJ_EXECUTED", "", "перевод исполнен (MS010)")
depoEvent(rc, guid, "", "BJ_ORDER_ACCEPTED", "", "")
depoEvent(rc, guid, "", "BJ_EXECUTED", "", "")
case "rejected":
depoEvent(rc, guid, "", "BJ_ORDER_REJECTED", "", "НРД отверг депо-поручение: "+text)
depoEvent(rc, guid, "", "BJ_ORDER_REJECTED", "", text)
case "late":
depoEvent(rc, guid, "", "BJ_ORDER_LATE", "", "отчёт MS010 не получен за 5 минут")
depoEvent(rc, guid, "", "BJ_ORDER_LATE", "", "")
case "expired":
depoEvent(rc, guid, "", "BJ_CANCELLED", "", "истёк срок 30 дней без исполнения — перевод считается отменённым")
depoEvent(rc, guid, "", "BJ_CANCELLED", "", "")
}
}
@@ -398,7 +406,7 @@ func depoOnQuik(rc *RuntimeConfig, guid, isin, evt, instrNo string) {
if typ == "" {
return
}
depoEvent(rc, guid, "", typ, "", "QUIK МНП: поручение №"+instrNo+", "+isin)
depoEvent(rc, guid, "", typ, "", instrNo)
}
// ---- чтение событий DEPO ----
@@ -414,55 +422,10 @@ func depoPollEvents(ctx context.Context, rc *RuntimeConfig) {
if !depoTableExists(cctx, db, "FTV_M2M_EVENTS") {
return
}
docCol := "NULL"
if depoHasColumn(cctx, db, "FTV_M2M", "DOC") {
docCol = "(SELECT TOP 1 CAST(m.DOC AS varchar(32)) FROM dbo.FTV_M2M m WHERE m.GUID=e.GUID)"
} else if depoHasColumn(cctx, db, "FTV_M2M", "FANSY_DOC") {
docCol = "(SELECT TOP 1 CAST(m.FANSY_DOC AS varchar(32)) FROM dbo.FTV_M2M m WHERE m.GUID=e.GUID)"
}
// Событий от bj2depo нет (17.09.2026: мастер — bj): номер документа — FTV_M2M.DOC, результат
// обработки — STATE/COMMENT наших событий и шапки.
depoPollOrders(cctx, db, rc)
depoAlertEventFailures(cctx, db, rc)
rows, err := db.QueryContext(cctx, `SELECT TOP 100 e.ID, CAST(e.GUID AS varchar(36)), ISNULL(e.REFERENCE,''), e.EVENT_TYPE, ISNULL(e.COMMENT,''), ISNULL(e.M2M_REJECT_CODE,''),
ISNULL(`+docCol+`,'')
FROM dbo.FTV_M2M_EVENTS e WHERE e.EVENT_TYPE LIKE 'DEPO[_]%' AND ISNULL(e.STATE,'TODO')='TODO' ORDER BY e.ID`)
if err != nil {
log.Printf("depo-events: чтение: %v", err)
return
}
type ev struct {
id int64
guid, ref, typ, comment, codes, doc string
}
var list []ev
for rows.Next() {
var e ev
if rows.Scan(&e.id, &e.guid, &e.ref, &e.typ, &e.comment, &e.codes, &e.doc) == nil {
list = append(list, e)
}
}
rows.Close()
for _, e := range list {
state, note := "SUCCESS", "применено в bj"
switch e.typ {
case "DEPO_ORDER_CREATED":
doc := strings.TrimSpace(e.comment)
if e.doc != "" && e.doc != "0" {
doc = e.doc
}
globalDepoLinks.update(e.guid, func(l *DepoLink) { l.DocNo, l.DocAt, l.LastEvent = doc, time.Now(), e.typ })
log.Printf("depo-events: DEPO создала поручение по %s: %s", shortGUID(e.guid), doc)
case "DEPO_ERROR":
globalDepoLinks.update(e.guid, func(l *DepoLink) {
l.Errors = append(l.Errors, time.Now().Format("02.01 15:04")+" "+e.comment)
l.LastEvent = e.typ
})
raiseSystemAlert(rc, "DEPO: ошибка обработки перевода "+shortGUID(e.guid), e.comment)
default:
state, note = "ERROR", "тип события не поддерживается bj"
}
_, _ = db.ExecContext(cctx, "UPDATE dbo.FTV_M2M_EVENTS SET STATE=@s, TimeBeg=ISNULL(TimeBeg,GETDATE()), TimeEnd=GETDATE(), COMMENT=ISNULL(COMMENT,'')+@n WHERE ID=@id",
sql.Named("s", state), sql.Named("n", " | bj: "+note), sql.Named("id", e.id))
}
}
// depoPollOrders — джоба bj2depo ставит номер поручения DEPO прямо в FTV_M2M.DOC (и DOL по строкам),
@@ -745,7 +708,7 @@ ALTER ROLE SQLAgentOperatorRole ADD MEMBER %[1]s;
func depoJobPing(ctx context.Context, db *sql.DB, wait time.Duration) (bool, string) {
guid := strings.ToUpper(fmt.Sprintf("%08x-0000-4000-8000-%012x", time.Now().Unix()&0xFFFFFFFF, time.Now().UnixNano()&0xFFFFFFFFFFFF))
if _, err := db.ExecContext(ctx, "INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, STATE, EVENT_TYPE, EVENT_DATE, COMMENT) VALUES (@g, NULL, 'TODO', 'BJ_PING', GETDATE(), N'проверка обработчика из bj')", sql.Named("g", guid)); err != nil {
if _, err := db.ExecContext(ctx, "INSERT INTO dbo.FTV_M2M_EVENTS (GUID, REFERENCE, EVENT_TYPE, EVENT_DATE) VALUES (@g, NULL, 'BJ_PING', GETDATE())", sql.Named("g", guid)); err != nil {
return false, "не удалось записать проверочное событие: " + quikErrorDetail(err)
}
defer db.ExecContext(context.Background(), "DELETE FROM dbo.FTV_M2M_EVENTS WHERE GUID=@g", sql.Named("g", guid))
@@ -898,27 +861,27 @@ func depoReplayHistory(ctx context.Context, rc *RuntimeConfig, svc *Service, onl
cp := handbookNameByCode(it.SenderCode)
depoUpsertOrder(rc, it.GUID, "IN", it.SenderCode, name, strings.TrimSpace(it.DocSeries+" "+it.DocNumber), it.DocSeries, it.DocNumber, lines, "TEST3: входящий от "+it.SenderCode+" "+cp)
nIn++
ev(it.ReceivedAt, it.GUID, "", "BJ_RECEIVED", "", "входящий перевод получен от "+it.SenderCode+" "+cp)
ev(it.ReceivedAt, it.GUID, "", "BJ_RECEIVED", "", "")
if it.Status == "answered" {
for _, s := range it.Securities {
switch s.Decision {
case "confirmed":
ev(it.AnsweredAt, it.GUID, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "приём подтверждён контрагенту")
ev(it.AnsweredAt, it.GUID, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "")
case "rejected":
ev(it.AnsweredAt, it.GUID, s.ReferenceID, "BJ_DECISION_REJECT", s.RejectCode, "отказ контрагенту: "+m2mCodeLabel(s.RejectCode))
ev(it.AnsweredAt, it.GUID, s.ReferenceID, "BJ_DECISION_REJECT", s.RejectCode, m2mCodeLabel(s.RejectCode))
}
}
}
if it.OrderSubmitted {
ev(it.OrderAt, it.GUID, "", "BJ_ORDER_SENT", "", "подано депо-поручение 16/1 (зачисление), CONTR_D_ID "+it.OrderContrDID)
ev(it.OrderAt, it.GUID, "", "BJ_ORDER_SENT", "", it.OrderContrDID)
resps := findOrderResponses(it.OrderContrDID)
if done, rejected, text := orderRespVerdict(resps); done {
at := orderRespAt(resps)
if rejected {
ev(at, it.GUID, "", "BJ_ORDER_REJECTED", "", "НРД отверг депо-поручение: "+text)
ev(at, it.GUID, "", "BJ_ORDER_REJECTED", "", text)
} else {
ev(at, it.GUID, "", "BJ_ORDER_ACCEPTED", "", "НРД принял депо-поручение (отчёт MS010)")
ev(at, it.GUID, "", "BJ_EXECUTED", "", "перевод исполнен (MS010)")
ev(at, it.GUID, "", "BJ_ORDER_ACCEPTED", "", "")
ev(at, it.GUID, "", "BJ_EXECUTED", "", "")
}
}
}
@@ -957,14 +920,14 @@ func depoReplayHistory(ctx context.Context, rc *RuntimeConfig, svc *Service, onl
name := strings.TrimSpace(strings.Join([]string{inv.LastName, inv.FirstName, inv.MiddleName}, " "))
depoUpsertOrder(rc, guid, "OUT", cp, name, strings.TrimSpace(ser+" "+num), ser, num, lines, "TEST3: исходящий, получатель "+cp+" "+handbookNameByCode(cp))
nOut++
ev(d.CreatedAt, guid, "", "BJ_SENT", "", "запрос на перевод отправлен в НРД")
ev(d.CreatedAt, guid, "", "BJ_SENT", "", "")
if d.Decision != nil {
for _, sec := range d.Decision.Data.Securities {
ref := string(sec.ReferenceID)
if sec.TransferDecision.Rejection != nil {
ev(d.UpdatedAt, guid, ref, "BJ_CP_REJECT", strings.Join(sec.TransferDecision.Rejection.Codes, ";"), "отказ принимающего депозитария: "+rejectCodesLabel(sec.TransferDecision.Rejection.Codes))
ev(d.UpdatedAt, guid, ref, "BJ_CP_REJECT", strings.Join(sec.TransferDecision.Rejection.Codes, ";"), rejectCodesLabel(sec.TransferDecision.Rejection.Codes))
} else if sec.TransferDecision.Confirmation != nil {
ev(d.UpdatedAt, guid, ref, "BJ_CP_CONFIRM", "", "принимающий депозитарий подтвердил")
ev(d.UpdatedAt, guid, ref, "BJ_CP_CONFIRM", "", "")
}
}
}
@@ -973,19 +936,19 @@ func depoReplayHistory(ctx context.Context, rc *RuntimeConfig, svc *Service, onl
for _, rr := range d.Response.Responses {
codes = append(codes, string(rr.Code))
}
ev(d.UpdatedAt, guid, "", "BJ_SERVICE_ERROR", strings.Join(codes, ";"), "отклонено сервисом MOEX МОСТ: "+rejectCodesLabel(codes))
ev(d.UpdatedAt, guid, "", "BJ_SERVICE_ERROR", strings.Join(codes, ";"), rejectCodesLabel(codes))
}
if globalOutgoingStore != nil {
if od, ok := scanForm16Deal(globalOutgoingStore, guid); ok && od.Submitted {
ev(od.OrderAt, guid, "", "BJ_ORDER_SENT", "", "подано депо-поручение 16 (списание), CONTR_D_ID "+strings.Join(od.ContrDIDs, ", "))
ev(od.OrderAt, guid, "", "BJ_ORDER_SENT", "", strings.Join(od.ContrDIDs, ";"))
resps, _ := outgoingResponses(od.ContrDIDs)
if done, rejected, text := orderRespVerdict(resps); done {
at := orderRespAt(resps)
if rejected {
ev(at, guid, "", "BJ_ORDER_REJECTED", "", "НРД отверг депо-поручение: "+text)
ev(at, guid, "", "BJ_ORDER_REJECTED", "", text)
} else {
ev(at, guid, "", "BJ_ORDER_ACCEPTED", "", "НРД принял депо-поручение (отчёт MS010)")
ev(at, guid, "", "BJ_EXECUTED", "", "перевод исполнен (MS010)")
ev(at, guid, "", "BJ_ORDER_ACCEPTED", "", "")
ev(at, guid, "", "BJ_EXECUTED", "", "")
}
}
}
@@ -1028,24 +991,24 @@ func depoSimulateIncoming(ctx context.Context, rc *RuntimeConfig, it *IncomingTr
return "", fmt.Errorf("не удалось записать шапку в FTV_M2M")
}
now := time.Now()
depoEventAt(rc, now, guid, "", "BJ_RECEIVED", "", "входящий перевод получен от "+it.SenderCode+" "+cp+" (тест по образцу "+shortGUID(it.GUID)+")")
depoEventAt(rc, now, guid, "", "BJ_RECEIVED", "", "")
anyConfirmed := false
for _, s := range it.Securities {
switch s.Decision {
case "confirmed":
anyConfirmed = true
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "приём подтверждён контрагенту")
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "")
case "rejected":
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_REJECT", s.RejectCode, "отказ контрагенту: "+m2mCodeLabel(s.RejectCode))
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_REJECT", s.RejectCode, m2mCodeLabel(s.RejectCode))
default:
anyConfirmed = true
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "приём подтверждён контрагенту (тест)")
depoEventAt(rc, now.Add(time.Minute), guid, s.ReferenceID, "BJ_DECISION_CONFIRM", "", "")
}
}
if anyConfirmed {
depoEventAt(rc, now.Add(2*time.Minute), guid, "", "BJ_ORDER_SENT", "", "подано депо-поручение 16/1 (зачисление) в НРД (тест)")
depoEventAt(rc, now.Add(3*time.Minute), guid, "", "BJ_ORDER_ACCEPTED", "", "НРД принял депо-поручение (отчёт MS010) (тест)")
depoEventAt(rc, now.Add(3*time.Minute), guid, "", "BJ_EXECUTED", "", "перевод исполнен (MS010) (тест)")
depoEventAt(rc, now.Add(2*time.Minute), guid, "", "BJ_ORDER_SENT", "", "")
depoEventAt(rc, now.Add(3*time.Minute), guid, "", "BJ_ORDER_ACCEPTED", "", "")
depoEventAt(rc, now.Add(3*time.Minute), guid, "", "BJ_EXECUTED", "", "")
}
return guid, nil
}