126 lines
4.2 KiB
Go
126 lines
4.2 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"
|
|
"github.com/tech/sendico/pkg/model/account_role"
|
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const LedgerTopologyVersion = 1
|
|
|
|
var RequiredRolesV1 = []account_role.AccountRole{
|
|
account_role.AccountRoleOperating,
|
|
account_role.AccountRoleHold,
|
|
account_role.AccountRolePending,
|
|
account_role.AccountRoleTransit,
|
|
account_role.AccountRoleSettlement,
|
|
}
|
|
|
|
func isRequiredTopologyRole(role account_role.AccountRole) bool {
|
|
for _, required := range RequiredRolesV1 {
|
|
if role == required {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Service) ensureLedgerTopology(ctx context.Context, orgRef bson.ObjectID, currency string) error {
|
|
if s.storage == nil || s.storage.Accounts() == nil {
|
|
return errStorageNotInitialized
|
|
}
|
|
if orgRef.IsZero() {
|
|
return merrors.InvalidArgument("organization_ref is required")
|
|
}
|
|
normalizedCurrency := strings.ToUpper(strings.TrimSpace(currency))
|
|
if normalizedCurrency == "" {
|
|
return merrors.InvalidArgument("currency is required")
|
|
}
|
|
|
|
for _, role := range RequiredRolesV1 {
|
|
if _, err := s.ensureRoleAccount(ctx, orgRef, normalizedCurrency, role); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) ensureRoleAccount(ctx context.Context, orgRef bson.ObjectID, currency string, role account_role.AccountRole) (*pmodel.LedgerAccount, error) {
|
|
if s.storage == nil || s.storage.Accounts() == nil {
|
|
return nil, errStorageNotInitialized
|
|
}
|
|
if orgRef.IsZero() {
|
|
return nil, merrors.InvalidArgument("organization_ref is required")
|
|
}
|
|
normalizedCurrency := strings.ToUpper(strings.TrimSpace(currency))
|
|
if normalizedCurrency == "" {
|
|
return nil, merrors.InvalidArgument("currency is required")
|
|
}
|
|
if strings.TrimSpace(string(role)) == "" {
|
|
return nil, merrors.InvalidArgument("role is required")
|
|
}
|
|
|
|
account, err := s.storage.Accounts().GetByRole(ctx, orgRef, normalizedCurrency, role)
|
|
if err == nil {
|
|
return account, nil
|
|
}
|
|
if !errors.Is(err, storage.ErrAccountNotFound) {
|
|
s.logger.Warn("Failed to resolve ledger account by role", zap.Error(err),
|
|
mzap.ObjRef("organization_ref", orgRef), zap.String("currency", normalizedCurrency),
|
|
zap.String("role", string(role)))
|
|
return nil, merrors.Internal("failed to resolve ledger account")
|
|
}
|
|
|
|
account = newSystemAccount(orgRef, normalizedCurrency, role)
|
|
if err := s.storage.Accounts().Create(ctx, account); err != nil {
|
|
if errors.Is(err, merrors.ErrDataConflict) {
|
|
existing, lookupErr := s.storage.Accounts().GetByRole(ctx, orgRef, normalizedCurrency, role)
|
|
if lookupErr == nil && existing != nil {
|
|
return existing, nil
|
|
}
|
|
s.logger.Warn("Duplicate ledger account create but failed to load existing",
|
|
zap.Error(lookupErr),
|
|
mzap.ObjRef("organization_ref", orgRef),
|
|
zap.String("currency", normalizedCurrency),
|
|
zap.String("role", string(role)))
|
|
return nil, merrors.Internal("failed to resolve ledger account after conflict")
|
|
}
|
|
s.logger.Warn("Failed to create system ledger account", zap.Error(err),
|
|
mzap.ObjRef("organization_ref", orgRef), zap.String("currency", normalizedCurrency),
|
|
zap.String("role", string(role)), zap.String("account_code", account.AccountCode))
|
|
return nil, merrors.Internal("failed to create ledger account")
|
|
}
|
|
|
|
s.logger.Info("System ledger account created", mzap.ObjRef("organization_ref", orgRef),
|
|
zap.String("currency", normalizedCurrency), zap.String("role", string(role)),
|
|
zap.String("account_code", account.AccountCode))
|
|
return account, nil
|
|
}
|
|
|
|
func newSystemAccount(orgRef bson.ObjectID, currency string, role account_role.AccountRole) *pmodel.LedgerAccount {
|
|
ref := bson.NewObjectID()
|
|
account := &pmodel.LedgerAccount{
|
|
AccountCode: generateAccountCode(pmodel.LedgerAccountTypeAsset, currency, ref),
|
|
AccountType: pmodel.LedgerAccountTypeAsset,
|
|
Currency: currency,
|
|
Status: pmodel.LedgerAccountStatusActive,
|
|
AllowNegative: false,
|
|
Role: role,
|
|
Scope: pmodel.LedgerAccountScopeOrganization,
|
|
Metadata: map[string]string{
|
|
"system": "true",
|
|
},
|
|
}
|
|
account.OrganizationRef = &orgRef
|
|
account.SetID(ref)
|
|
return account
|
|
}
|