Files
Bridge-and-Join-s/internal/m2mcore/ports.go
fontvielle 9e6e95f431 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>
2026-05-14 00:52:12 +03:00

131 lines
4.5 KiB
Go

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
}