package lkgateway // incoming_export.go — выгрузка входящего перевода для контролирующих органов // (аналог выгрузки по исходящим заявкам): ZIP с отчётом-доказательством, исходным // XML запроса контрагента и XML нашего ответа-решения. Фиксирует весь путь: // когда запрос сформирован, когда мы забрали его из шлюза, когда ответили. import ( "archive/zip" "fmt" "html" "net/http" "strings" "time" ) // exportIncoming отдаёт ZIP-пакет доказательств по одному входящему переводу. func exportIncoming(w http.ResponseWriter, r *http.Request, deps incomingDeps, id string) { it, ok := deps.store.Get(id) if !ok { http.Error(w, "входящий перевод не найден", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="vhodyaschiy-%s.zip"`, shortID(it.ID))) zw := zip.NewWriter(w) defer zw.Close() if f, e := zw.Create("otchet.html"); e == nil { _, _ = f.Write(incomingReportHTML(it)) } if it.RawXML != "" { if f, e := zw.Create("request.xml"); e == nil { _, _ = f.Write(faithfulXMLBytes(it.RawXML)) } } if it.DecisionXML != "" { if f, e := zw.Create("decision.xml"); e == nil { _, _ = f.Write(faithfulXMLBytes(it.DecisionXML)) } } if it.OrderXML != "" { if f, e := zw.Create("poruchenie-16-1.xml"); e == nil { _, _ = f.Write(faithfulXMLBytes(it.OrderXML)) } } // Ответ НРД по поручению (отчёт MS010 + квитанции) — сырые файлы из INBOX. for _, rf := range collectOrderResponseRaw(it.OrderContrDID) { if f, e := zw.Create("otvet-nrd/" + rf.Name); e == nil { _, _ = f.Write(rf.Data) } } } // incomingReportHTML формирует HTML-отчёт-доказательство по входящему переводу. func incomingReportHTML(it *IncomingTransfer) []byte { esc := html.EscapeString msk := func(t time.Time) string { if t.IsZero() { return "—" } return t.In(time.FixedZone("МСК", 3*3600)).Format("02.01.2006 15:04:05") + " МСК" } dur := func(a, b time.Time) string { if a.IsZero() || b.IsZero() { return "—" } d := b.Sub(a) if d < 0 { d = -d } return d.Round(time.Second).String() } senderName := handbookNameByCode(it.SenderCode) fio := strings.TrimSpace(it.InvestorLast + " " + it.InvestorFirst + " " + it.InvestorMiddle) var b strings.Builder b.WriteString(``) b.WriteString(`Отчёт о входящем переводе M2M`) b.WriteString(`
` + moexMostLogoSVG + `Сервис быстрых переводов ценных бумаг
MOEX МОСТ · НКО АО НРД
` + orgLogoIMG() + `
`) b.WriteString(`

Отчёт о входящем переводе ценных бумаг (M2M / MOEX МОСТ)

`) b.WriteString(`
Доказательство для контролирующих органов. Внутренний id bj: ` + esc(it.ID) + ` · Сформировано: ` + time.Now().In(time.FixedZone("МСК", 3*3600)).Format("02.01.2006 15:04:05") + ` МСК
`) // Реквизиты b.WriteString(`

Реквизиты перевода

`) row := func(k, v string) { b.WriteString(``) } row("GUID перевода", ``+esc(it.GUID)+``) from := esc(it.SenderCode) if it.SenderINN != "" { from += ` · ИНН ` + esc(it.SenderINN) } if senderName != "" { from = `` + esc(senderName) + ` · ` + from } row("Передающий депозитарий (списывающий)", from) to := esc(it.ReceiverCode) if it.ReceivingINN != "" { to += ` · ИНН ` + esc(it.ReceivingINN) } row("Принимающий депозитарий (мы)", to) row("Инвестор (ФИО)", ``+esc(fio)+``) doc := esc(it.DocType) if it.DocSeries != "" { doc += ` · серия ` + esc(it.DocSeries) } if it.DocNumber != "" { doc += ` · № ` + esc(it.DocNumber) } row("Документ инвестора", doc) b.WriteString(`
` + k + `` + v + `
`) // Хронология (фиксация времени прохождения) b.WriteString(`

Хронология прохождения сообщения

`) row("Запрос сформирован отправителем", msk(it.RequestCreated)) row("Получено нами (забрано из шлюза ИШ)", msk(it.ReceivedAt)+` (время доставки: `+dur(it.RequestCreated, it.ReceivedAt)+`)`) if it.Status == "answered" { row("Ответ (решение) отправлен", msk(it.AnsweredAt)+` (время обработки: `+dur(it.ReceivedAt, it.AnsweredAt)+`)`) } else { row("Ответ (решение) отправлен", `— (ещё не отвечено)`) } status := "ждёт решения" if it.Status == "answered" { status = "отвечено" if it.Auto { status += " (автоматически, после проверки в Fancy)" } } row("Статус", esc(status)) if it.AnswerNote != "" { row("Результат", esc(it.AnswerNote)) } b.WriteString(`
`) // Бумаги b.WriteString(`

Ценные бумаги

`) b.WriteString(``) for _, s := range it.Securities { code := esc(s.SecurityCode) if s.ISIN != "" { code += ` / ` + esc(s.ISIN) } acc := "" if s.DeponentCode != "" { acc += `депонент ` + esc(s.DeponentCode) } if s.AccountID != "" { acc += `
счёт ` + esc(s.AccountID) } if s.SectionID != "" { acc += ` / раздел ` + esc(s.SectionID) } if s.SettleINN != "" { acc += `
расч.деп. ИНН ` + esc(s.SettleINN) } dec := "—" switch s.Decision { case "confirmed": dec = "принято (зачислено)" case "rejected": dec = "отказ · " + esc(m2mCodeLabel(s.RejectCode)) } b.WriteString(``) } b.WriteString(`
Код НРД / ISINКол-воСчёт зачисленияРешение
` + code + `` + esc(s.Quantity) + `` + acc + `` + dec + `
`) // Депозитарное исполнение: поручение 16/1 (ORDXM) + ответы НРД (движение). if it.OrderSubmitted { resps := findOrderResponses(it.OrderContrDID) var ms010At string for _, rp := range resps { if rp.Done { ms010At = rp.Time } } b.WriteString(`

Депозитарное исполнение (поручение 16/1) — хронология

`) if dr := draftReceivedTime(it.GUID); dr != "" { row("Черновик 16/1 получен от НРД (MF010)", esc(dr)+` МСК`) } row("Поручение 16/1 подано в НРД", msk(it.OrderAt)) if ms010At != "" { row("Отчёт MS010 получен (операция проведена)", esc(ms010At)+` МСК — бумаги зачислены`) } else { row("Отчёт MS010 получен", `— (ещё не получен)`) } row("Тип ЭД / пакет(ы) ИШ", "ORDXM · "+esc(it.OrderPkgs)) if it.OrderContrDID != "" { row("Номер поручения (contr_d_id)", esc(it.OrderContrDID)) } b.WriteString(`
`) if len(resps) > 0 { b.WriteString(`

Ответы НРД по поручению

`) b.WriteString(``) for _, rp := range resps { b.WriteString(``) } b.WriteString(`
Время полученияТип ответаПакет
` + esc(rp.Time) + ` МСК` + esc(rp.Kind) + `` + esc(rp.Pkg) + `
`) } if it.OrderXML != "" { b.WriteString(`

Отправленное поручение 16/1 (дословно)

` + esc(it.OrderXML) + `
`) } } // Все поля запроса (разбор XML) if fields := renderXMLFieldsTable(it.RawXML); fields != "" { b.WriteString(`

Все поля запроса (разбор XML)

` + fields) } // Приложения — сырые XML (также отдельными файлами в архиве) if it.RawXML != "" { b.WriteString(`

Исходный XML запроса контрагента (request.xml)

` + esc(it.RawXML) + `
`) } if it.DecisionXML != "" { b.WriteString(`

XML нашего ответа-решения (decision.xml)

` + esc(it.DecisionXML) + `
`) } b.WriteString(`

Отчёт сформирован автоматически системой Bridge-and-Join-s (bj). Логотип MOEX МОСТ — собственность НКО АО НРД.

`) b.WriteString(``) return []byte(b.String()) }