60b4d46277
«Исходящие», «Входящие», «Журнал обмена»: быстрые периоды (сегодня, 7, 30 дней, всё время), даты с–по, сортировка (время, инвестор/контрагент, направление, статус), первые 50 строк и подгрузка при прокрутке (part=rows&offset=N). Раньше показывались только последние 200/100. Анкета участника — исходящий пакет в НРД: строка в «Исходящих» и «Журнале» с ответом НРД (принята / отклонена с кодом / ждёт), вместо плашки над списком. Отправки копятся в participant-regs.json (прежний participant-reg.json подхватывается). userTemplate — функции текущего пользователя и для порций строк. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
package lkgateway
|
||
|
||
// participant_reg.go — учёт регистрации участника M2M: что и когда отправили
|
||
// анкетой (#M2MPF) и какой ответ дал НРД (сервис МОСТ, M2MTransferResponse с
|
||
// нулевым GUID — он не относится к переводу). Показывается на странице анкеты.
|
||
|
||
import (
|
||
"encoding/json"
|
||
"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
|
||
})
|
||
}
|