package lkgateway // participant_reg.go — учёт регистрации участника M2M: что и когда отправили // анкетой (#M2MPF) и какой ответ дал НРД (сервис МОСТ, M2MTransferResponse с // нулевым GUID — он не относится к переводу). Показывается на странице анкеты. import ( "encoding/json" "html/template" "os" "strings" "sync" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" ) const participantRegFile = "/var/lib/bj/.bj/participant-reg.json" // participantReg — последняя отправка анкеты и ответ НРД на неё. type participantReg struct { SentAt time.Time `json:"sent_at,omitempty"` PkgID string `json:"pkg_id,omitempty"` Name string `json:"name,omitempty"` // отправленное полное наименование Channel string `json:"channel,omitempty"` RespAt time.Time `json:"resp_at,omitempty"` Status string `json:"status,omitempty"` // OK | ERROR Codes []string `json:"codes,omitempty"` RespText string `json:"resp_text,omitempty"` RawFile string `json:"raw_file,omitempty"` } var participantRegMu sync.Mutex // participantRegsFile — все отправки анкеты (строки в «Исходящих» и «Журнале»); participantRegFile — // прежний файл с одной последней отправкой, из него история начинается при первом чтении. const participantRegsFile = "/var/lib/bj/.bj/participant-regs.json" func readParticipantRegs() []participantReg { var list []participantReg if b, err := os.ReadFile(participantRegsFile); err == nil { _ = json.Unmarshal(b, &list) return list } var r participantReg if b, err := os.ReadFile(participantRegFile); err == nil && json.Unmarshal(b, &r) == nil && !r.SentAt.IsZero() { list = append(list, r) } return list } func writeParticipantRegs(list []participantReg) { b, err := json.MarshalIndent(list, "", " ") if err != nil { return } tmp := participantRegsFile + ".tmp" if os.WriteFile(tmp, b, 0o640) == nil { _ = os.Rename(tmp, participantRegsFile) } } // loadParticipantRegs — все отправки анкеты, старые первыми. func loadParticipantRegs() []participantReg { participantRegMu.Lock() defer participantRegMu.Unlock() return readParticipantRegs() } // loadParticipantReg — последняя отправка анкеты. func loadParticipantReg() participantReg { list := loadParticipantRegs() if len(list) == 0 { return participantReg{} } return list[len(list)-1] } // addParticipantReg — новая отправка анкеты. func addParticipantReg(r participantReg) { participantRegMu.Lock() defer participantRegMu.Unlock() writeParticipantRegs(append(readParticipantRegs(), r)) } // saveParticipantReg — изменить последнюю отправку (ответ НРД приходит на неё). func saveParticipantReg(mut func(*participantReg)) { participantRegMu.Lock() defer participantRegMu.Unlock() list := readParticipantRegs() if len(list) == 0 { list = append(list, participantReg{}) } mut(&list[len(list)-1]) writeParticipantRegs(list) } // StatusClass, StatusText — статус отправки анкеты для строк списков (класс бейджа и текст). func (r participantReg) StatusClass() string { switch { case r.RespAt.IsZero(): return "awaiting_decision" case strings.EqualFold(r.Status, "ERROR"): return "rejected" } return "confirmed" } func (r participantReg) StatusText() string { switch { case r.RespAt.IsZero(): return "Ждёт ответа НРД" case strings.EqualFold(r.Status, "ERROR"): if len(r.Codes) > 0 { return "Отклонена НРД · " + strings.Join(r.Codes, ", ") } return "Отклонена НРД" } return "Принята НРД" } // recordParticipantReply сохраняет ответ НРД на анкету (статус, коды, тексты, файл XML). func recordParticipantReply(status string, resp *m2m.M2MTransferResponse, rawFile string) { var codes []string seen := map[string]bool{} for _, rr := range resp.Responses { c := strings.TrimSpace(rr.Code) if c != "" && !seen[c] { seen[c] = true codes = append(codes, c) } } text := strings.Join(serviceErrorTexts(resp), "; ") saveParticipantReg(func(r *participantReg) { r.RespAt = time.Now() r.Status = status r.Codes = codes r.RespText = text r.RawFile = rawFile }) } // participantAccepted — последняя отправленная анкета принята НРД. func participantAccepted() bool { r := loadParticipantReg() return !r.RespAt.IsZero() && !strings.EqualFold(r.Status, "ERROR") } // participantBanner — плашка наверху раздела НРД: анкета не отправлена, ждёт ответа или отклонена. // Пропадает, когда НРД принял анкету. Анкету шлём только в боевом контуре — в тестовом плашки нет. func participantBanner() template.HTML { if participantAccepted() || contourOf(currentNSDProfile()) != "prod" { return "" } esc := template.HTMLEscapeString r := loadParticipantReg() cls, head, body := "stc-err", "Анкета участника M2M не отправлена", "Без регистрации участника НРД не принимает запросы на переводы. Проверьте анкету и отправьте её." switch { case r.SentAt.IsZero(): case r.RespAt.IsZero(): cls, head, body = "stc-run", "Анкета участника M2M ждёт ответа НРД", "Отправлена "+r.SentAt.In(listMSK).Format("02.01.2006 15:04")+". Плашка пропадёт, когда НРД примет анкету." case strings.EqualFold(r.Status, "ERROR"): cls, head = "stc-err", "НРД отклонил анкету участника M2M" body = r.RespAt.In(listMSK).Format("02.01.2006 15:04") + ": " + r.RespText + ". Исправьте и отправьте снова." } return template.HTML(`
` + esc(head) + `` + esc(body) + ` Открыть анкету →
`) }