Анкета участника: ответ НРД принимается автоматически — квитанция и ответ сервиса
Квитанции ЭДО НРД (пакеты C, «Уведомление о получении Пакетов…») разбираются: для нашей анкеты (#M2MPF…) — отметка «доставлена в НРД» с временем, ошибка первичной обработки — отказ с текстом квитанции. Ответ сервиса: INFO — «принята в обработку», OK — принята, ERROR — отказ с кодом; документ-ответ M2MTransferParticipantForm — регистрация подтверждена. Всё видно в строке «Исходящих», на странице анкеты и в плашке раздела НРД. После запуска bj перечитывает квитанции за день отправки, если отметки ещё нет (22.10 на 9 квитанция пришла раньше). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
package lkgateway
|
||||
|
||||
// nsdreceipt.go — квитанции ЭДО НРД («Уведомление о получении Пакетов электронных документов», пакеты
|
||||
// типа C: HTML + подпись, без M2M-XML). По ним видно, что НРД получил наш пакет и как прошла первичная
|
||||
// обработка (тип, расшифровка, распаковка). Для анкеты участника это первая отметка «доставлена»;
|
||||
// ответ по существу (принята / M2M37) приходит отдельно.
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
)
|
||||
|
||||
// nsdReceipt — разобранная квитанция о получении пакета.
|
||||
type nsdReceipt struct {
|
||||
Package string // имя нашего пакета, напр. #M2MPF2210260000000000071.ZIP
|
||||
ReceivedAt time.Time // когда НРД получил пакет
|
||||
OK bool // первичная обработка без ошибок
|
||||
Text string // кратко — что не прошло (если не OK)
|
||||
}
|
||||
|
||||
var (
|
||||
rcptTagRe = regexp.MustCompile(`(?s)<[^>]*>`)
|
||||
rcptPkgRe = regexp.MustCompile(`#[A-Z0-9]+\.ZIP`)
|
||||
rcptTimeRe = regexp.MustCompile(`Дата и время получения\s+(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2})`)
|
||||
rcptBadRe = regexp.MustCompile(`(?i)(ошибк\S*|не\s*успешно|неуспешно|отказ\S*|не\s+выполнен\S*)`)
|
||||
)
|
||||
|
||||
// parseNSDReceipt — квитанция из ZIP пакета C. false — это не квитанция о получении.
|
||||
func parseNSDReceipt(zipBytes []byte) (nsdReceipt, bool) {
|
||||
zr, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
|
||||
if err != nil {
|
||||
return nsdReceipt{}, false
|
||||
}
|
||||
for _, f := range zr.File {
|
||||
if !strings.HasSuffix(strings.ToLower(f.Name), ".html") && !strings.HasSuffix(strings.ToLower(f.Name), ".htm") {
|
||||
continue
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
raw, _ := io.ReadAll(io.LimitReader(rc, 1<<20))
|
||||
rc.Close()
|
||||
s := string(raw)
|
||||
if !strings.Contains(s, "Уведомление") { // квитанции НРД — в windows-1251
|
||||
if d, err := charmap.Windows1251.NewDecoder().Bytes(raw); err == nil {
|
||||
s = string(d)
|
||||
}
|
||||
}
|
||||
return parseNSDReceiptText(s)
|
||||
}
|
||||
return nsdReceipt{}, false
|
||||
}
|
||||
|
||||
func parseNSDReceiptText(html string) (nsdReceipt, bool) {
|
||||
text := strings.Join(strings.Fields(rcptTagRe.ReplaceAllString(html, " ")), " ")
|
||||
if !strings.Contains(text, "Уведомление о получении") {
|
||||
return nsdReceipt{}, false
|
||||
}
|
||||
r := nsdReceipt{Package: rcptPkgRe.FindString(text)}
|
||||
if m := rcptTimeRe.FindStringSubmatch(text); m != nil {
|
||||
r.ReceivedAt, _ = time.ParseInLocation("02.01.2006 15:04:05", m[1], listMSK)
|
||||
}
|
||||
res := text
|
||||
if i := strings.Index(text, "Результаты первичной обработки"); i >= 0 {
|
||||
res = text[i:]
|
||||
}
|
||||
if j := strings.Index(res, "Составлено"); j > 0 {
|
||||
res = res[:j]
|
||||
}
|
||||
if m := rcptBadRe.FindStringIndex(res); m != nil {
|
||||
a, b := m[0]-80, m[1]+120
|
||||
if a < 0 {
|
||||
a = 0
|
||||
}
|
||||
if b > len(res) {
|
||||
b = len(res)
|
||||
}
|
||||
r.Text = strings.TrimSpace(strings.ToValidUTF8(res[a:b], ""))
|
||||
} else {
|
||||
r.OK = strings.Contains(res, "Успешно")
|
||||
}
|
||||
return r, r.Package != ""
|
||||
}
|
||||
|
||||
// applyNSDReceipt — квитанция на нашу анкету (#M2MPF…): отметка «доставлена» в последней отправке;
|
||||
// ошибка первичной обработки — отказ с текстом квитанции.
|
||||
func applyNSDReceipt(r nsdReceipt) bool {
|
||||
if !strings.HasPrefix(r.Package, "#M2MPF") {
|
||||
return false
|
||||
}
|
||||
applied := false
|
||||
saveParticipantReg(func(rg *participantReg) {
|
||||
if rg.SentAt.IsZero() || !rg.DeliveredAt.IsZero() || (!r.ReceivedAt.IsZero() && r.ReceivedAt.Before(rg.SentAt.Add(-time.Minute))) {
|
||||
return // квитанция на прежнюю отправку
|
||||
}
|
||||
applied = true
|
||||
rg.DeliveredAt, rg.Package = r.ReceivedAt, r.Package
|
||||
if rg.DeliveredAt.IsZero() {
|
||||
rg.DeliveredAt = time.Now()
|
||||
}
|
||||
if !r.OK && rg.RespAt.IsZero() {
|
||||
rg.RespAt, rg.Status, rg.RespText = time.Now(), "ERROR", "квитанция НРД: "+pick(r.Text, "пакет не прошёл первичную обработку")
|
||||
}
|
||||
})
|
||||
return applied
|
||||
}
|
||||
|
||||
// participantReceiptBackfill — после запуска: квитанция на анкету могла прийти, когда bj ещё
|
||||
// пропускал квитанции (или был остановлен) — перечитываем входящие за день отправки.
|
||||
func (s *Server) participantReceiptBackfill(ctx context.Context) {
|
||||
rg := loadParticipantReg()
|
||||
if rg.SentAt.IsZero() || !rg.DeliveredAt.IsZero() || time.Since(rg.SentAt) > 72*time.Hour || s.igwClient == nil {
|
||||
return
|
||||
}
|
||||
cctx, cancel := context.WithTimeout(ctx, 60*time.Second)
|
||||
defer cancel()
|
||||
pkgs, err := s.igwClient.ListIncoming(cctx, igw.ListFilter{Channel: s.igwChannel, Date: rg.SentAt})
|
||||
if err != nil {
|
||||
log.Printf("lk-gateway: квитанция на анкету: ListIncoming: %v", err)
|
||||
return
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
if !strings.EqualFold(p.Type, "C") {
|
||||
continue
|
||||
}
|
||||
b, err := s.igwClient.GetPackage(cctx, p.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if r, ok := parseNSDReceipt(b); ok && applyNSDReceipt(r) {
|
||||
log.Printf("lk-gateway: квитанция на анкету найдена id=%d: %s получен НРД %s, без ошибок=%v", p.ID, r.Package, r.ReceivedAt.Format("02.01 15:04:05"), r.OK)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user