c476cfd7a3
- Карточка заявки: операторский «Путь заявки» (claimTimelineOperator) короткими понятными вехами + сворачиваемый технический журнал (buildClaimTimeline); QUIK показан пошагово (register/sign/deny/deposit) с верными временами, отказ регистрации больше не выглядит успехом (quikActionFailTitles). - Баннеры: «в НРД не отправлено», когда пакет сформирован, но не ушёл (нет submitted_to_nsd и не Fail); заголовок карточки и бейдж учитывают это; красный баннер QUIK при отказе, строгий «нет бумаг» только при реальном шорт/недостатке. - НРД-ответ «Поручение с исходящим номером … уже существует» теперь REG_DUP (предупреждение-дубль, не красный «Отказ в регистрации»); не влияет на od.Rejected. - Idempotency: submitguard.go (inflightSubmit) + защита точек подачи от двойного срабатывания (manual + auto-loop, двойной клик): submitOutgoingFor, submitIncomingOrderFor, QuikOutRegister (ранний возврат при registered/signed). - DEPO-алерты: водяной знак depoFailAlertedID засеивается текущим MAX(ID) на старте процесса (Hlth03) — после рестарта bj больше не поднимает повторно алерты про уже известные ошибки обработки событий BJ_*. - Тесты: claimnotsent_render_test.go (хелперы + рендер карточки). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XG3zNMMf2LMoDpXo22mwMX
271 lines
12 KiB
Go
271 lines
12 KiB
Go
// Package lkgateway реализует REST API контракта ESIA Finance V1
|
|
// (docs/lk-contract/v1/openapi.yaml) и admin web-интерфейс.
|
|
package lkgateway
|
|
|
|
import (
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
|
)
|
|
|
|
// CreateClaimRequest — DTO входа POST /api/v1/back_office/claims/.
|
|
type CreateClaimRequest struct {
|
|
Investor Investor `json:"investor"`
|
|
TransferringDepositoryINN string `json:"transferring_depository_inn"`
|
|
ReceivingDepositoryINN string `json:"receiving_depository_inn"`
|
|
CostInfo CostInfo `json:"cost_info"`
|
|
IIAAgreement *IIAAgreement `json:"iia_agreement,omitempty"`
|
|
Securities []ClaimSec `json:"securities"`
|
|
SignedDocument string `json:"signed_document"`
|
|
SignatureFormat string `json:"signature_format"`
|
|
// ReceiverCodeOverride — если задан, переопределяет код получателя
|
|
// (Header.ReceiverCode). Используется для тестовых пакетов роботу НРД
|
|
// (MC0012500000). Пусто = берётся defaultReceiver.
|
|
ReceiverCodeOverride string `json:"receiver_code_override,omitempty"`
|
|
// InvestorDocumentOverride — если задан, переопределяет документ инвестора
|
|
// из анкеты. Используется тестом с роботом НРД (серия ДУЛ = код сценария).
|
|
InvestorDocumentOverride *Document `json:"investor_document_override,omitempty"`
|
|
// ClientAccount — торговый счёт депо клиента из заявки (ЛК): сверяется с DEPO —
|
|
// должен принадлежать найденному по паспорту инвестору и быть открыт.
|
|
ClientAccount string `json:"client_account,omitempty"`
|
|
}
|
|
|
|
// Investor — анкета.
|
|
type Investor struct {
|
|
ID string `json:"id,omitempty"`
|
|
LastName string `json:"last_name"`
|
|
FirstName string `json:"first_name"`
|
|
MiddleName string `json:"middle_name,omitempty"`
|
|
Document Document `json:"document"`
|
|
BirthDate string `json:"birth_date,omitempty"` // ГГГГ-ММ-ДД (из карточки пользователя ЛК), сверяется с DEPO
|
|
}
|
|
|
|
// Document — удостоверение личности.
|
|
type Document struct {
|
|
DocumentType string `json:"document_type"`
|
|
Series string `json:"series,omitempty"`
|
|
Number string `json:"number"`
|
|
}
|
|
|
|
// CostInfo — choice yes|no.
|
|
type CostInfo struct {
|
|
Yes *CostInfoYes `json:"yes,omitempty"`
|
|
No *struct{} `json:"no,omitempty"`
|
|
}
|
|
|
|
// CostInfoYes — учёт ведётся, с кодом депонента-источника.
|
|
type CostInfoYes struct {
|
|
Code string `json:"code"`
|
|
}
|
|
|
|
// IIAAgreement — реквизиты договора ИИС.
|
|
type IIAAgreement struct {
|
|
AgreementType string `json:"agreement_type"`
|
|
AgreementNumber string `json:"agreement_number"`
|
|
AgreementDate string `json:"agreement_date"`
|
|
BrokerINN string `json:"broker_inn"`
|
|
}
|
|
|
|
// ClaimSec — одна ЦБ в заявке.
|
|
type ClaimSec struct {
|
|
SecurityCode string `json:"security_code"`
|
|
SecurityDetails SecurityDetails `json:"security_details"`
|
|
Quantity Quantity `json:"quantity"`
|
|
SettlementAccounts []SettlementAccount `json:"settlement_accounts"`
|
|
}
|
|
|
|
// SecurityDetails — choice isin|security_info.
|
|
type SecurityDetails struct {
|
|
ISIN string `json:"isin,omitempty"`
|
|
SecurityInfo *SecurityInfo `json:"security_info,omitempty"`
|
|
}
|
|
|
|
// SecurityInfo — описание ЦБ без ISIN.
|
|
type SecurityInfo struct {
|
|
Classification string `json:"classification"`
|
|
Category string `json:"category"`
|
|
SecurityType string `json:"security_type,omitempty"`
|
|
SecuritySeries string `json:"security_series,omitempty"`
|
|
IdentificationDetails IdentificationDetails `json:"identification_details"`
|
|
}
|
|
|
|
// IdentificationDetails — choice reg_number|fund_shares.
|
|
type IdentificationDetails struct {
|
|
RegNumber string `json:"reg_number,omitempty"`
|
|
FundShares *FundShares `json:"fund_shares,omitempty"`
|
|
}
|
|
|
|
// FundShares — ПИФ.
|
|
type FundShares struct {
|
|
RegNumber string `json:"reg_number"`
|
|
Class string `json:"class,omitempty"`
|
|
}
|
|
|
|
// Quantity — choice whole|fractional.
|
|
type Quantity struct {
|
|
Whole uint64 `json:"whole,omitempty"`
|
|
Fractional string `json:"fractional,omitempty"`
|
|
}
|
|
|
|
// SettlementAccount — реквизиты счёта.
|
|
type SettlementAccount struct {
|
|
SettlementRequisitesINN string `json:"settlement_requisites_inn"`
|
|
SettlementLocation SettlementLocation `json:"settlement_location"`
|
|
}
|
|
|
|
// SettlementLocation — место хранения.
|
|
type SettlementLocation struct {
|
|
DeponentCode string `json:"deponent_code"`
|
|
AccountID string `json:"account_id"`
|
|
SectionID string `json:"section_id"`
|
|
}
|
|
|
|
// CreateClaimResponse — DTO ответа POST.
|
|
type CreateClaimResponse struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
Success bool `json:"success"`
|
|
}
|
|
|
|
// ClaimView — полная заявка с историей (GET и admin).
|
|
type ClaimView struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
Investor Investor `json:"investor"`
|
|
TransferringDepositoryINN string `json:"transferring_depository_inn"`
|
|
ReceivingDepositoryINN string `json:"receiving_depository_inn"`
|
|
CostInfo CostInfo `json:"cost_info"`
|
|
IIAAgreement *IIAAgreement `json:"iia_agreement,omitempty"`
|
|
Securities []ClaimSec `json:"securities"`
|
|
LastCallback *StatusCallback `json:"last_callback,omitempty"`
|
|
Stages []StageView `json:"stages,omitempty"`
|
|
M2MGUID m2m.UUID `json:"m2m_guid,omitempty"`
|
|
M2MResponse *NSDResponseView `json:"m2m_response,omitempty"`
|
|
M2MDecision *NSDDecisionView `json:"m2m_decision,omitempty"`
|
|
// RequestRawXML — точный M2MTransferRequest, отправленный в НРД
|
|
// (декодирован в UTF-8). Чтобы в карточке видеть отправленный пакет.
|
|
RequestRawXML string `json:"request_raw_xml,omitempty"`
|
|
// TestResult — для тестовых заявок роботу НРД: что ожидали по сценарию,
|
|
// что получили, и совпало ли. Nil для обычных (не тестовых) заявок.
|
|
TestResult *TestResultView `json:"test_result,omitempty"`
|
|
// Timeline — путь заявки во времени: положена в БД Fancy → вердикт «крана»
|
|
// → отправлена в шлюз НРД → ответ НРД. С длительностями между этапами.
|
|
// На экране карточки это КРАТКИЙ операторский путь (claimTimelineOperator);
|
|
// в выгрузках и JSON — полный (buildClaimTimeline).
|
|
Timeline []TimelineEvent `json:"timeline,omitempty"`
|
|
// TimelineTech — полный технический путь (все события: FTV/Fancy, QUIK,
|
|
// поручение 16). Показывается в карточке под сворачиваемым «Технический журнал».
|
|
TimelineTech []TimelineEvent `json:"timeline_tech,omitempty"`
|
|
}
|
|
|
|
// TimelineEvent — один шаг в истории движения заявки.
|
|
type TimelineEvent struct {
|
|
Title string `json:"title"` // что произошло
|
|
Time time.Time `json:"time"` // когда
|
|
Detail string `json:"detail,omitempty"` // подробность (кто/что/причина)
|
|
Since string `json:"since,omitempty"` // сколько прошло с предыдущего шага (человекочит.)
|
|
Kind string `json:"kind"` // db | crane | gateway | response | reject
|
|
}
|
|
|
|
// TestResultView — самопроверка тестовой заявки роботу НРД: сценарий,
|
|
// ожидаемый и фактический результат, вердикт.
|
|
type TestResultView struct {
|
|
Scenario string `json:"scenario"` // 1111 / 2001 / 2002 / 3333
|
|
Title string `json:"title"` // человекочитаемое название сценария
|
|
Expected string `json:"expected"` // что ждём от робота
|
|
Got string `json:"got"` // что реально пришло
|
|
Status string `json:"status"` // ok | mismatch | waiting | error
|
|
Note string `json:"note,omitempty"` // пояснение
|
|
}
|
|
|
|
// statusFromBool ставит ok/mismatch и пояснение по результату сравнения.
|
|
func (t *TestResultView) statusFromBool(ok bool, okNote, badNote string) {
|
|
if ok {
|
|
t.Status = "ok"
|
|
t.Note = okNote
|
|
} else {
|
|
t.Status = "mismatch"
|
|
t.Note = badNote
|
|
}
|
|
}
|
|
|
|
// StageView — точка истории FSM для UI.
|
|
type StageView struct {
|
|
State string `json:"state"`
|
|
EnteredAt time.Time `json:"entered_at"`
|
|
LeftAt *time.Time `json:"left_at,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
// StatusCallback — callback статуса от lk-gateway к ЛК.
|
|
type StatusCallback struct {
|
|
ClaimID string `json:"claim_id"`
|
|
NewStatus string `json:"new_status"`
|
|
ReasonCode string `json:"reason_code,omitempty"`
|
|
ReasonText string `json:"reason_text,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
NSDResponse *NSDResponseView `json:"nsd_response,omitempty"`
|
|
}
|
|
|
|
// NSDResponseView — сжатое представление M2MTransferResponse для UI/callback.
|
|
type NSDResponseView struct {
|
|
GUID string `json:"guid"`
|
|
StatusCode string `json:"status_code"`
|
|
Responses []NSDResponseEntry `json:"responses"`
|
|
// RawXML — точные байты ответа МОСТ от НРД (декодированные в UTF-8 для
|
|
// показа). Для дословной пересылки в техподдержку НРД.
|
|
RawXML string `json:"raw_xml,omitempty"`
|
|
}
|
|
|
|
// NSDResponseEntry — одна запись Response.
|
|
type NSDResponseEntry struct {
|
|
ReferenceID string `json:"reference_id,omitempty"`
|
|
Code string `json:"code"`
|
|
Text string `json:"text,omitempty"`
|
|
}
|
|
|
|
// NSDDecisionView — сжатое представление M2MTransferDecision для UI.
|
|
type NSDDecisionView struct {
|
|
GUID string `json:"guid"`
|
|
Securities []NSDDecisionSecurity `json:"securities"`
|
|
}
|
|
|
|
// NSDDecisionSecurity — решение по одной ЦБ.
|
|
type NSDDecisionSecurity struct {
|
|
ReferenceID string `json:"reference_id"`
|
|
Outcome string `json:"outcome"` // "confirmed" | "rejected"
|
|
RejectCodes []string `json:"reject_codes,omitempty"`
|
|
}
|
|
|
|
// ErrorResponse — формат ошибки, идентичный ESIA Finance.
|
|
type ErrorResponse struct {
|
|
Error bool `json:"error"`
|
|
Status int `json:"status"`
|
|
Code string `json:"code"`
|
|
Title string `json:"title"`
|
|
Meta *ErrorMeta `json:"meta,omitempty"`
|
|
}
|
|
|
|
// ErrorMeta — детали ошибки.
|
|
type ErrorMeta struct {
|
|
Message string `json:"message,omitempty"`
|
|
Errors []FieldErrorDetail `json:"errors,omitempty"`
|
|
}
|
|
|
|
// FieldErrorDetail — ошибка по конкретному полю.
|
|
type FieldErrorDetail struct {
|
|
Field string `json:"field"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// ClaimsPage — постраничная выдача.
|
|
type ClaimsPage struct {
|
|
Items []ClaimView `json:"items"`
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
}
|