Путь заявки из всех источников: сделка, QUIK, события DEPO (с обработкой/ошибкой), поручение 16 и документы НРД (регистрация, GS036, GS116 сквитовано, MS010); «Обновлена» — последнее событие; статус учитывает сквитовку (GS116), время MS010 в строке статуса
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -38,6 +38,8 @@ type OrderWatch struct {
|
||||
// NoCounterAt — НРД прислал уведомление GS036 «об отсутствии встречного поручения»: контрагент
|
||||
// ещё не подал своё поручение (к нашей 16 — его 16/1, к нашей 16/1 — его 16). Сбрасывается MS010.
|
||||
NoCounterAt time.Time `json:"no_counter_at,omitempty"`
|
||||
// MatchedAt — НРД сквитовал поручения со встречными (GS116, MACH) по всем строкам: дальше — исполнение.
|
||||
MatchedAt time.Time `json:"matched_at,omitempty"`
|
||||
// Lines — состояние по каждой строке пакета (одно депо-поручение = одна бумага),
|
||||
// ключ — contr_d_id. По договорённости с Fancy (18.09.2026) исходы пишутся в DEPO
|
||||
// по строкам с REFERENCE и сразу по MS010 этой строки, не дожидаясь остальных:
|
||||
@@ -53,6 +55,7 @@ type OrderLineWatch struct {
|
||||
Rejected bool `json:"rejected,omitempty"`
|
||||
ExpiredAt time.Time `json:"expired_at,omitempty"`
|
||||
NoCounterAt time.Time `json:"no_counter_at,omitempty"` // GS036 по этой строке
|
||||
MatchedAt time.Time `json:"matched_at,omitempty"` // GS116 MACH по этой строке
|
||||
}
|
||||
|
||||
// counterName — чьё встречное поручение ждём: к нашей 16 — 16/1 принимающего, к нашей 16/1 — 16 передающего.
|
||||
@@ -120,14 +123,19 @@ func (w *OrderWatch) StatusRu() string {
|
||||
case !w.MS010At.IsZero():
|
||||
late := ""
|
||||
if w.MS010At.Sub(w.OrderAt) > orderMS010Within {
|
||||
late = fmt.Sprintf(", с опозданием %s", w.MS010At.Sub(w.OrderAt).Round(time.Minute))
|
||||
late = ", с опозданием " + humanDur(w.MS010At.Sub(w.OrderAt))
|
||||
}
|
||||
if !w.NoCounterAt.IsZero() {
|
||||
late += "; задержка на стороне контрагента — встречное " + w.counterName() + " подано не сразу (уведомление НРД " + w.NoCounterAt.Local().Format("15:04") + ")"
|
||||
switch {
|
||||
case !w.MatchedAt.IsZero() && w.MS010At.Sub(w.MatchedAt) > orderMS010Within:
|
||||
late += "; сквитовано со встречным в " + w.MatchedAt.Local().Format("15:04") + ", исполнение НРД — через " + humanDur(w.MS010At.Sub(w.MatchedAt))
|
||||
case w.MatchedAt.IsZero() && !w.NoCounterAt.IsZero():
|
||||
late += "; встречное " + w.counterName() + " подано не сразу (уведомление НРД " + w.NoCounterAt.Local().Format("15:04") + ")"
|
||||
}
|
||||
return "исполнено: MS010 получен " + w.MS010At.Local().Format("02.01.2006 15:04") + late
|
||||
case !w.ExpiredAt.IsZero():
|
||||
return "СРОК 30 ДНЕЙ ИСТЁК " + w.ExpiredAt.Local().Format("02.01.2006") + " — поручение считается отменённым"
|
||||
case !w.MatchedAt.IsZero():
|
||||
return "поручение сквитовано со встречным (НРД " + w.MatchedAt.Local().Format("02.01 15:04") + "), ждём исполнения НРД (MS010)"
|
||||
case !w.NoCounterAt.IsZero():
|
||||
return "НРД: контрагент ещё не подал встречное " + w.counterName() + " (уведомление НРД " + w.NoCounterAt.Local().Format("02.01 15:04") + "); наше поручение принято, ждём исполнения"
|
||||
case !w.LateAt.IsZero():
|
||||
@@ -341,7 +349,9 @@ func orderWatchCheck(ctx context.Context, rc *RuntimeConfig, guid, direction str
|
||||
}
|
||||
if fresh {
|
||||
why := "отчёт MS010 не получен"
|
||||
if !w.NoCounterAt.IsZero() {
|
||||
if !w.MatchedAt.IsZero() {
|
||||
why = "поручения сквитованы со встречными (" + w.MatchedAt.Local().Format("15:04") + "), ждём исполнения НРД"
|
||||
} else if !w.NoCounterAt.IsZero() {
|
||||
why = "НРД сообщил, что контрагент ещё не подал встречное " + w.counterName() + " — наше поручение принято, исполнение ждёт контрагента"
|
||||
}
|
||||
raiseSystemAlert(rc, "НРД: депо-поручение не исполнено в срок 5 минут",
|
||||
@@ -489,6 +499,36 @@ func orderWatchRefs(guid string) []string {
|
||||
func orderNoteNoCounter(guid string, w *OrderWatch, lines []orderLine) {
|
||||
for _, ln := range lines {
|
||||
st := w.Lines[ln.ContrDID]
|
||||
// отчёт GS116 «сквитовано» (MACH) — по номеру поручения
|
||||
if st == nil || st.MatchedAt.IsZero() {
|
||||
if at, ok := findMatchedNotice(ln.ContrDID); ok {
|
||||
globalOrderWatch.update(guid, func(w *OrderWatch) {
|
||||
if w.Lines == nil {
|
||||
w.Lines = map[string]*OrderLineWatch{}
|
||||
}
|
||||
l := w.Lines[ln.ContrDID]
|
||||
if l == nil {
|
||||
l = &OrderLineWatch{Ref: ln.Ref, ISIN: ln.ISIN}
|
||||
w.Lines[ln.ContrDID] = l
|
||||
}
|
||||
l.MatchedAt = at
|
||||
all, last := true, time.Time{}
|
||||
for _, x := range lines {
|
||||
lx := w.Lines[x.ContrDID]
|
||||
if lx == nil || lx.MatchedAt.IsZero() {
|
||||
all = false
|
||||
break
|
||||
}
|
||||
if lx.MatchedAt.After(last) {
|
||||
last = lx.MatchedAt
|
||||
}
|
||||
}
|
||||
if all {
|
||||
w.MatchedAt = last
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// уведомление «нет встречного поручения» (GS036) — по REFERENCE строки
|
||||
if (st == nil || st.NoCounterAt.IsZero()) && ln.Ref != "" {
|
||||
if at, ok := findNoCounterNotice(ln.Ref); ok {
|
||||
@@ -511,3 +551,16 @@ func orderNoteNoCounter(guid string, w *OrderWatch, lines []orderLine) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findMatchedNotice — отчёт НРД GS116 «сквитовано со встречным» (<Mtchd>) по номеру поручения.
|
||||
func findMatchedNotice(contrDID string) (time.Time, bool) {
|
||||
if contrDID == "" {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, pk := range pkgIndex() {
|
||||
if strings.Contains(pk.body, "<Mtchd>") && strings.Contains(pk.body, "GS116") && strings.Contains(pk.body, ">"+contrDID+"<") {
|
||||
return pk.mt, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user