ЛК: итог «Исполнена» по MS010, GUID без учёта регистра, заявка при отказе МНП берётся один раз; тестовый контур не трогает боевой ЛК

Перевод 97EAD00C (заявка ЛК #71741, 23.10.2026) исполнен в QUIK и НРД (MS010),
но в ЛК статус не изменился, а сделка осталась «Подтверждена»:
- связки guid → заявка ЛК искались с учётом регистра: мы шлём GUID строчными,
  НРД отвечает заглавными — OnLKOutcome молча не находил заявку. Ключи —
  строчными (lkGUIDKey), старые записи приводятся при загрузке; итог модуля
  ЛК (lk.transfer_requests) — lower(guid);
- после MS010 по всем строкам поручения 16 сделка → «Исполнена» (CompleteSUB16),
  итог — в ЛК (outOnExecuted, каждый проход order-watch, повтор безвреден);
  в бою согласие контрагента — ещё не «Исполнена»: статус ждёт MS010, как в
  тесте «боевой ЛК → TEST3»; имитатор НРД — итог сразу.

Тестовый 28 с боевым ЛК взял ту же заявку и после отказа МНП («запрещен для
операции шорт» — бумагу уже заблокировал 9) брал её каждые 10 секунд: 108
сделок с новыми GUID и столько же писем.
- Сделка заведена, а отправка не прошла (МНП не заблокировал) — заявка
  помечается взятой и связывается с переводом: дальше — оператор в карточке.
- Боевой ЛК при тестовом профиле ИШ (lkProdOnTestNSD): заявки клиентов не
  берутся (кроме теста «боевой ЛК → тестовый НРД»), портфели не заполняются,
  предупреждение в настройках ЛК.
- Письма: в теме и первой строке — сервер bj и контур НРД («[bj ТЕСТ …]»
  у тестового); письма о входящих — метка только у тестового.
- История FSM — по МСК с датой, «текущее состояние» вместо «сейчас».
- Карточка теста «боевой ЛК → тестовый НРД» — только в режиме разработки
  (включённый тест виден всегда, чтобы его выключить).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-23 16:04:44 +03:00
parent ffd69b0698
commit 32d88fc42a
11 changed files with 333 additions and 20 deletions
+43
View File
@@ -18,6 +18,7 @@ import (
"sync"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
)
@@ -421,6 +422,9 @@ func orderWatchTick(ctx context.Context, rc *RuntimeConfig, svc *Service) {
continue
}
orderWatchCheck(ctx, rc, od.GUID, "out", od.OrderAt, outgoingLineRefs(ctx, svc, od), "Поручение 16 (списание)")
if w := globalOrderWatch.Get(od.GUID); w != nil && !w.MS010At.IsZero() && time.Since(w.MS010At) < 7*24*time.Hour {
outOnExecuted(ctx, rc, svc, od.GUID, w)
}
}
}
if globalIncomingStore != nil {
@@ -433,6 +437,45 @@ func orderWatchTick(ctx context.Context, rc *RuntimeConfig, svc *Service) {
}
}
// outOnExecuted — НРД закрыл MS010 все строки нашего поручения 16: сделка → «Исполнена» (если списана
// хоть одна бумага), итог — в ЛК ЕСИА Финанс. Вызывается каждый проход, повтор ничего не меняет
// (23.10.2026: после MS010 сделка 97EAD00C оставалась «Подтверждена», в ЛК статус не менялся).
// Тест «боевой ЛК → TEST3» — свой итог (realTestOnExecuted).
func outOnExecuted(ctx context.Context, rc *RuntimeConfig, svc *Service, guid string, w *OrderWatch) {
total, executed, rejected, _ := w.linesSummary()
if total == 0 && !w.Rejected { // старые записи без строк — одна строка
total, executed = 1, 1
}
if svc != nil && executed > 0 {
d, err := svc.Repo().GetByGUID(ctx, m2m.UUID(guid))
if err != nil {
d, err = svc.Repo().GetByGUID(ctx, m2m.UUID(strings.ToLower(guid)))
}
if err == nil && (d.State == m2mcore.StateConfirmed || d.State == m2mcore.StateAwaitingSUB16) {
if err := d.CompleteSUB16(ctx); err != nil {
log.Printf("order-watch: %s: перевод не отмечен исполненным: %v", shortGUID(guid), err)
} else if err := svc.Repo().Update(ctx, d); err != nil {
log.Printf("order-watch: %s: сохранение «Исполнена»: %v", shortGUID(guid), err)
} else {
log.Printf("order-watch: %s: поручение 16 исполнено (MS010) — перевод «Исполнена»", shortGUID(guid))
}
}
}
if realTestGet(guid) != nil || !lkLinked(guid) {
return
}
status, reason := "done", "Перевод исполнен: бумаги списаны"
switch {
case executed == 0:
status, reason = "rejected", "НРД не исполнил поручение на списание"
case rejected > 0:
reason = fmt.Sprintf("Перевод исполнен частично: исполнено %d из %d бумаг", executed, total)
}
globalLKFlows.stepIfExists(guid, pick3(status == "done", "response", "reject"), "Итог НРД (MS010) → статус в ЛК", reason)
OnLKOutcome(ctx, rc, guid, status, reason)
log.Printf("order-watch: %s: итог в ЛК — %s (%s)", shortGUID(guid), status, reason)
}
// incomingOrderLines — строки поданных 16/1. Старые записи (до строчных событий) знали
// только первый contr_d_id — для них одна строка без REFERENCE.
func incomingOrderLines(it *IncomingTransfer) []orderLine {