Отправка в НРД с подтверждением доставки: ERROR от ИШ — заявка «Не отправлена», без BJ_SENT; «Отправить повторно» тем же GUID; сторож связи ИШ↔НРД по журналу ИШ

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-25 15:57:07 +03:00
parent eecb3b7923
commit c2aaa09fe3
15 changed files with 651 additions and 33 deletions
+36 -3
View File
@@ -51,6 +51,7 @@ var templateFuncs = template.FuncMap{
"quikCodeMap": quikClientCodeMapText,
"curUser": authTemplateFuncs["curUser"],
"can": authTemplateFuncs["can"],
"sendFailText": sendFailText,
"devOn": authTemplateFuncs["devOn"],
"devLicensed": authTemplateFuncs["devLicensed"],
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
@@ -172,7 +173,7 @@ func russianState(s string) string {
case "draft":
return "Черновик"
case "validated":
return "Валидирована"
return "Не отправлена"
case "submitted_to_nsd":
return "Отправлена в НРД"
case "awaiting_decision":
@@ -360,6 +361,7 @@ type page struct {
NPR bool // модуль «НПР / УДС» включён и разрешён лицензией
IGWDown string // ИШ НРД не отвечает: «N мин» (пусто — отвечает или не проверялся)
IGWDetail string
NSDDown string // ИШ отвечает, но НРД не отвечает шлюзу (nsdlinkwatch.go)
DevMode bool // режим разработки (сервер лицензий): видны служебные разделы
}
@@ -407,6 +409,7 @@ type claimData struct {
DepoL *DepoLink
Depo *OutgoingDeal // депозитарное поручение 16 (списание) по этой сделке, если черновик пришёл
Flash string
Fail *SendFailure // последняя неудачная отправка в НРД (sendfail.go)
}
// statusData — данные страницы статуса.
@@ -446,6 +449,9 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts
case strings.HasPrefix(p, "claims/") && strings.HasSuffix(p, "/export.zip"):
id := strings.TrimSuffix(strings.TrimPrefix(p, "claims/"), "/export.zip")
a.exportClaim(w, r, svc, id)
case strings.HasPrefix(p, "claims/") && strings.HasSuffix(p, "/resend"):
id := strings.TrimSuffix(strings.TrimPrefix(p, "claims/"), "/resend")
a.resendClaim(w, r, svc, id)
case strings.HasPrefix(p, "claims/") && strings.HasSuffix(p, "/delete-draft"):
id := strings.TrimSuffix(strings.TrimPrefix(p, "claims/"), "/delete-draft")
a.deleteDraft(w, r, svc, id)
@@ -551,7 +557,7 @@ func (a *admin) deleteDraft(w http.ResponseWriter, r *http.Request, svc *Service
back("/admin/claims", "Заявка не найдена")
return
}
if d.State != m2mcore.StateDraft {
if !m2mcore.IsUnsent(d.State) {
back("/admin/claims/"+id, "Удалить можно только черновик: эта заявка уже ушла в НРД")
return
}
@@ -586,6 +592,27 @@ func (a *admin) deleteDraft(w http.ResponseWriter, r *http.Request, svc *Service
back("/admin/claims", msg)
}
// resendClaim — POST /admin/claims/{id}/resend: заявка не дошла до НРД (контур был недоступен) —
// отправить тот же запрос (тот же GUID) ещё раз.
func (a *admin) resendClaim(w http.ResponseWriter, r *http.Request, svc *Service, id string) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
id = path.Base(id)
msg := "✓ Отправлено повторно: НРД принял пакет"
if err := svc.ResendClaim(r.Context(), id); err != nil {
msg = "✗ Повторная отправка не удалась: " + err.Error()
} else {
actor := ""
if u := currentUser(w); u != nil {
actor = u.Login
}
log.Printf("admin: заявка %s отправлена в НРД повторно (%s)", id, actor)
}
http.Redirect(w, r, "/admin/claims/"+id+"?flash="+url.QueryEscape(msg), http.StatusSeeOther)
}
func (a *admin) renderClaim(w http.ResponseWriter, r *http.Request, svc *Service, id string) {
id = path.Base(id)
view, err := svc.GetClaim(r.Context(), id)
@@ -612,8 +639,12 @@ func (a *admin) renderClaim(w http.ResponseWriter, r *http.Request, svc *Service
if view.M2MGUID != "" {
depoL = globalDepoLinks.Get(string(view.M2MGUID))
}
var fail *SendFailure
if view.M2MGUID != "" {
fail = sendFailGet(string(view.M2MGUID))
}
render(w, a.claim, claimData{
Quik: quik, Watch: watch, DepoL: depoL,
Quik: quik, Watch: watch, DepoL: depoL, Fail: fail,
page: nowPage("Заявка", "claims"),
Claim: view, Depo: depo, Flash: r.URL.Query().Get("flash"),
})
@@ -777,6 +808,8 @@ func nowPage(title, active string) page {
p.DevMode = devMode(globalRC)
if down, d, det := igwStatus(); down {
p.IGWDown, p.IGWDetail = humanDur(d), det
} else if down, det := nsdLinkStatus(); down {
p.NSDDown, p.IGWDetail = det, det
}
s := globalRC.Snapshot()
switch {