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.
if docs := nsdOrderDocs(it.OrderContrDID); len(docs) > 0 {
writeNSDDocsZip(zw, "Ответ НРД/", "Документы НРД по поручению 16/1 (зачисление) № "+it.OrderContrDID, docs, true)
}
}
// 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(`| ` + k + ` | ` + v + ` |
`) }
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(`
`)
// Хронология (фиксация времени прохождения)
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(`| Код НРД / ISIN | Кол-во | Счёт зачисления | Решение |
`)
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(`| ` + code + ` | ` + esc(s.Quantity) + ` | ` + acc + ` | ` + dec + ` |
`)
}
b.WriteString(`
`)
// Депозитарное исполнение: поручение 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(`| ` + esc(rp.Time) + ` МСК | ` + esc(rp.Kind) + ` | ` + esc(rp.Pkg) + ` |
`)
}
b.WriteString(`
`)
}
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())
}