148 lines
4.4 KiB
Go
148 lines
4.4 KiB
Go
package ledger
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/ledger/storage"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
pmodel "github.com/tech/sendico/pkg/model"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
// EnsureSystemAccounts initializes required system accounts once at startup.
|
|
func (s *Service) EnsureSystemAccounts(ctx context.Context) error {
|
|
return s.ensureSystemAccounts(ctx)
|
|
}
|
|
|
|
func (s *Service) ensureSystemAccounts(ctx context.Context) error {
|
|
if s == nil || s.storage == nil || s.storage.Accounts() == nil {
|
|
return errStorageNotInitialized
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
for _, currency := range pmodel.SupportedCurrencies {
|
|
normalized := strings.ToUpper(strings.TrimSpace(string(currency)))
|
|
if normalized == "" {
|
|
continue
|
|
}
|
|
if err := s.ensureSystemAccountForCurrency(ctx, pmodel.SystemAccountPurposeExternalSource, normalized); err != nil {
|
|
return err
|
|
}
|
|
if err := s.ensureSystemAccountForCurrency(ctx, pmodel.SystemAccountPurposeExternalSink, normalized); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) ensureSystemAccountForCurrency(ctx context.Context, purpose pmodel.SystemAccountPurpose, currency string) error {
|
|
account, err := s.storage.Accounts().GetSystemAccount(ctx, purpose, currency)
|
|
if err == nil && account != nil {
|
|
s.cacheSystemAccount(purpose, currency, account)
|
|
return nil
|
|
}
|
|
if err != nil && !errors.Is(err, storage.ErrAccountNotFound) {
|
|
return err
|
|
}
|
|
|
|
account = newExternalSystemAccount(purpose, currency)
|
|
if err := s.storage.Accounts().Create(ctx, account); err != nil {
|
|
if errors.Is(err, merrors.ErrDataConflict) {
|
|
existing, lookupErr := s.storage.Accounts().GetSystemAccount(ctx, purpose, currency)
|
|
if lookupErr != nil {
|
|
return lookupErr
|
|
}
|
|
s.cacheSystemAccount(purpose, currency, existing)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
s.cacheSystemAccount(purpose, currency, account)
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) systemAccount(ctx context.Context, purpose pmodel.SystemAccountPurpose, currency string) (*pmodel.LedgerAccount, error) {
|
|
if s == nil || s.storage == nil || s.storage.Accounts() == nil {
|
|
return nil, errStorageNotInitialized
|
|
}
|
|
normalized := strings.ToUpper(strings.TrimSpace(currency))
|
|
if normalized == "" {
|
|
return nil, merrors.InvalidArgument("currency is required")
|
|
}
|
|
|
|
if acc := s.cachedSystemAccount(purpose, normalized); acc != nil {
|
|
return acc, nil
|
|
}
|
|
|
|
account, err := s.storage.Accounts().GetSystemAccount(ctx, purpose, normalized)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.cacheSystemAccount(purpose, normalized, account)
|
|
return account, nil
|
|
}
|
|
|
|
func (s *Service) cachedSystemAccount(purpose pmodel.SystemAccountPurpose, currency string) *pmodel.LedgerAccount {
|
|
s.systemAccounts.mu.RLock()
|
|
defer s.systemAccounts.mu.RUnlock()
|
|
|
|
switch purpose {
|
|
case pmodel.SystemAccountPurposeExternalSource:
|
|
if s.systemAccounts.externalSource == nil {
|
|
return nil
|
|
}
|
|
return s.systemAccounts.externalSource[currency]
|
|
case pmodel.SystemAccountPurposeExternalSink:
|
|
if s.systemAccounts.externalSink == nil {
|
|
return nil
|
|
}
|
|
return s.systemAccounts.externalSink[currency]
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *Service) cacheSystemAccount(purpose pmodel.SystemAccountPurpose, currency string, account *pmodel.LedgerAccount) {
|
|
if account == nil {
|
|
return
|
|
}
|
|
s.systemAccounts.mu.Lock()
|
|
defer s.systemAccounts.mu.Unlock()
|
|
|
|
switch purpose {
|
|
case pmodel.SystemAccountPurposeExternalSource:
|
|
if s.systemAccounts.externalSource == nil {
|
|
s.systemAccounts.externalSource = make(map[string]*pmodel.LedgerAccount)
|
|
}
|
|
s.systemAccounts.externalSource[currency] = account
|
|
case pmodel.SystemAccountPurposeExternalSink:
|
|
if s.systemAccounts.externalSink == nil {
|
|
s.systemAccounts.externalSink = make(map[string]*pmodel.LedgerAccount)
|
|
}
|
|
s.systemAccounts.externalSink[currency] = account
|
|
}
|
|
}
|
|
|
|
func newExternalSystemAccount(purpose pmodel.SystemAccountPurpose, currency string) *pmodel.LedgerAccount {
|
|
ref := bson.NewObjectID()
|
|
purposeCopy := purpose
|
|
account := &pmodel.LedgerAccount{
|
|
AccountCode: generateAccountCode(pmodel.LedgerAccountTypeAsset, currency, ref),
|
|
AccountType: pmodel.LedgerAccountTypeAsset,
|
|
Currency: currency,
|
|
Status: pmodel.LedgerAccountStatusActive,
|
|
AllowNegative: true,
|
|
Scope: pmodel.LedgerAccountScopeSystem,
|
|
SystemPurpose: &purposeCopy,
|
|
Metadata: map[string]string{
|
|
"system": "true",
|
|
},
|
|
}
|
|
account.SetID(ref)
|
|
return account
|
|
}
|