feat(m2m-core): FSM сделки, репозиторий, идемпотентность по GUID, метрики SLA
- internal/m2mcore/fsm.go: конечный автомат с переходами Draft → Validated → SubmittedToNSD → AwaitingDecision → Confirmed → AwaitingSUB16 → Done, ветки Rejected/TimedOut/ManualApproval - internal/m2mcore/deal.go: доменная модель Deal с методами Validate/Submit/ReceiveDecision/Timeout/SendToManualApproval/ApproveManually/RejectManually/CompleteSUB16, журнал событий - internal/m2mcore/uuid.go: генератор UUID v4 без внешних зависимостей (crypto/rand) - internal/m2mcore/repo.go: порт Repository + MemoryRepository с идемпотентным Create по GUID - internal/m2mcore/ports.go: порты NSDSender/LKCallbackClient/CryptoVerifier/FansyStore с no-op заглушками для M1 - internal/m2mcore/enrich.go: EnrichRequest — сборка M2MTransferRequest из ClaimInput + Fansy, генерация ReferenceID по каждой ЦБ - internal/m2mcore/metrics.go: порт Recorder + MemoryRecorder в Prometheus-text формате - cmd/m2m-core/main.go: HTTP-сервер с /healthz и /metrics, graceful shutdown - migrations/m2m-core/001__deals.sql: схема для PostgreSQL-Repository (для M2) Покрытие: 63.1%. make ci зелёный. Без внешних Go-зависимостей (pgx и prometheus подключим в M2, когда прокси zetit откроет Go-модули). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package m2mcore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
||||
)
|
||||
|
||||
// CertInfo — описание подписанта (заполняется CryptoVerifier).
|
||||
type CertInfo struct {
|
||||
SignerCN string
|
||||
SignerINN string
|
||||
Serial string
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
}
|
||||
|
||||
// Client — анкета инвестора, нужная для enrich Request.
|
||||
type Client struct {
|
||||
ID string
|
||||
LastName string
|
||||
FirstName string
|
||||
MiddleName string
|
||||
BirthDate *time.Time
|
||||
Document ClientDocument
|
||||
}
|
||||
|
||||
// ClientDocument — документ, удостоверяющий личность.
|
||||
type ClientDocument struct {
|
||||
DocumentType m2m.IdentityDocumentCode
|
||||
Series string
|
||||
Number string
|
||||
}
|
||||
|
||||
// DepoAccount — счёт депо инвестора у депозитария.
|
||||
type DepoAccount struct {
|
||||
ID string
|
||||
ClientID string
|
||||
DeponentCode string
|
||||
AccountID m2m.AccountID
|
||||
SectionID string
|
||||
DepositoryINN m2m.OrganizationINN
|
||||
}
|
||||
|
||||
// SecurityBalance — остаток по ценной бумаге на конкретном счёте депо.
|
||||
type SecurityBalance struct {
|
||||
SecurityCode m2m.SecurityCode
|
||||
ISIN m2m.ISIN
|
||||
QuantityWhole *uint64
|
||||
QuantityFractional *m2m.Decimal16
|
||||
IsolationStatus m2m.IsolationStatus
|
||||
ValuedAt time.Time
|
||||
}
|
||||
|
||||
// NSDSender — порт отправки в НРД (через ИШ или резервный канал WS ONYX).
|
||||
type NSDSender interface {
|
||||
// Send отправляет запрос на перевод и возвращает квитанцию НРД.
|
||||
Send(ctx context.Context, req *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error)
|
||||
// SendDecision отправляет решение принимающей стороны.
|
||||
SendDecision(ctx context.Context, decision *m2m.M2MTransferDecision) error
|
||||
}
|
||||
|
||||
// LKCallbackClient — порт уведомления ЛК клиента об изменении статуса.
|
||||
type LKCallbackClient interface {
|
||||
UpdateStatus(ctx context.Context, claimID, status, reason string) error
|
||||
}
|
||||
|
||||
// CryptoVerifier — порт проверки и формирования XMLDSig-подписей.
|
||||
type CryptoVerifier interface {
|
||||
VerifyXMLDSig(ctx context.Context, payload []byte) (CertInfo, error)
|
||||
}
|
||||
|
||||
// FansyStore — порт чтения данных из принимающей БД fansy-store.
|
||||
type FansyStore interface {
|
||||
GetClientByID(ctx context.Context, id string) (*Client, error)
|
||||
GetDepoAccounts(ctx context.Context, clientID string, depositoryINN m2m.OrganizationINN) ([]DepoAccount, error)
|
||||
GetBalances(ctx context.Context, depoAccountID string, codes []m2m.SecurityCode) ([]SecurityBalance, error)
|
||||
}
|
||||
|
||||
// ErrNotImplemented возвращается заглушками портов.
|
||||
var ErrNotImplemented = errors.New("m2mcore: не реализовано (M1 заглушка)")
|
||||
|
||||
// NoopNSDSender — заглушка NSDSender для M1.
|
||||
type NoopNSDSender struct{}
|
||||
|
||||
// Send возвращает ErrNotImplemented.
|
||||
func (NoopNSDSender) Send(context.Context, *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// SendDecision возвращает ErrNotImplemented.
|
||||
func (NoopNSDSender) SendDecision(context.Context, *m2m.M2MTransferDecision) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// NoopLKCallbackClient — заглушка LKCallbackClient для M1.
|
||||
type NoopLKCallbackClient struct{}
|
||||
|
||||
// UpdateStatus возвращает ErrNotImplemented.
|
||||
func (NoopLKCallbackClient) UpdateStatus(context.Context, string, string, string) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// NoopCryptoVerifier — заглушка CryptoVerifier для M1.
|
||||
type NoopCryptoVerifier struct{}
|
||||
|
||||
// VerifyXMLDSig возвращает ErrNotImplemented.
|
||||
func (NoopCryptoVerifier) VerifyXMLDSig(context.Context, []byte) (CertInfo, error) {
|
||||
return CertInfo{}, ErrNotImplemented
|
||||
}
|
||||
|
||||
// NoopFansyStore — заглушка FansyStore для M1.
|
||||
type NoopFansyStore struct{}
|
||||
|
||||
// GetClientByID возвращает ErrNotImplemented.
|
||||
func (NoopFansyStore) GetClientByID(context.Context, string) (*Client, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetDepoAccounts возвращает ErrNotImplemented.
|
||||
func (NoopFansyStore) GetDepoAccounts(context.Context, string, m2m.OrganizationINN) ([]DepoAccount, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetBalances возвращает ErrNotImplemented.
|
||||
func (NoopFansyStore) GetBalances(context.Context, string, []m2m.SecurityCode) ([]SecurityBalance, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
Reference in New Issue
Block a user