refactored payment orchestration

This commit is contained in:
Stephan D
2026-02-03 00:40:46 +01:00
parent 05d998e0f7
commit 5e87e2f2f9
184 changed files with 3920 additions and 2219 deletions

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/model/account_role"
"go.mongodb.org/mongo-driver/v2/bson"
)
@@ -40,7 +41,7 @@ type Account struct {
// Posting policy & lifecycle
AllowNegative bool `bson:"allowNegative" json:"allowNegative"`
Status model.LedgerAccountStatus `bson:"status" json:"status"`
Role model.AccountRole `bson:"role,omitempty" json:"role,omitempty"`
Role account_role.AccountRole `bson:"role,omitempty" json:"role,omitempty"`
// Legal ownership history
Ownerships []Ownership `bson:"ownerships,omitempty" json:"ownerships,omitempty"`
@@ -78,17 +79,17 @@ func (a *Account) Validate() error {
if role := strings.TrimSpace(string(a.Role)); role != "" {
switch a.Role {
case model.AccountRoleOperating,
model.AccountRoleHold,
model.AccountRoleTransit,
model.AccountRoleSettlement,
model.AccountRoleClearing,
model.AccountRolePending,
model.AccountRoleReserve,
model.AccountRoleLiquidity,
model.AccountRoleFee,
model.AccountRoleChargeback,
model.AccountRoleAdjustment:
case account_role.AccountRoleOperating,
account_role.AccountRoleHold,
account_role.AccountRoleTransit,
account_role.AccountRoleSettlement,
account_role.AccountRoleClearing,
account_role.AccountRolePending,
account_role.AccountRoleReserve,
account_role.AccountRoleLiquidity,
account_role.AccountRoleFee,
account_role.AccountRoleChargeback,
account_role.AccountRoleAdjustment:
default:
veAdd(&verr, "role", "invalid", "unknown account role")
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"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"
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
@@ -25,7 +26,7 @@ type createAccountParams struct {
currency string
modelType pmodel.LedgerAccountType
modelStatus pmodel.LedgerAccountStatus
modelRole pmodel.AccountRole
modelRole account_role.AccountRole
}
// validateCreateAccountInput validates and normalizes all fields from the request.
@@ -93,7 +94,7 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
}
// Non-settlement accounts require a settlement account to exist first.
if p.modelRole != pmodel.AccountRoleSettlement {
if p.modelRole != account_role.AccountRoleSettlement {
if _, err := s.ensureSettlementAccount(ctx, p.orgRef, p.currency); err != nil {
return nil, err
}
@@ -104,7 +105,7 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
}
// resolveTopologyAccount ensures ledger topology is initialized and returns the system account for the given role.
func (s *Service) resolveTopologyAccount(ctx context.Context, orgRef bson.ObjectID, currency string, role pmodel.AccountRole) (*ledgerv1.CreateAccountResponse, error) {
func (s *Service) resolveTopologyAccount(ctx context.Context, orgRef bson.ObjectID, currency string, role account_role.AccountRole) (*ledgerv1.CreateAccountResponse, error) {
if err := s.ensureLedgerTopology(ctx, orgRef, currency); err != nil {
recordAccountOperation("create", "error")
return nil, err
@@ -244,58 +245,58 @@ func modelAccountTypeToProto(t pmodel.LedgerAccountType) ledgerv1.AccountType {
}
}
func protoAccountRoleToModel(r ledgerv1.AccountRole) (pmodel.AccountRole, error) {
func protoAccountRoleToModel(r ledgerv1.AccountRole) (account_role.AccountRole, error) {
switch r {
case ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED:
return pmodel.AccountRoleOperating, nil
return account_role.AccountRoleOperating, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_HOLD:
return pmodel.AccountRoleHold, nil
return account_role.AccountRoleHold, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_TRANSIT:
return pmodel.AccountRoleTransit, nil
return account_role.AccountRoleTransit, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_SETTLEMENT:
return pmodel.AccountRoleSettlement, nil
return account_role.AccountRoleSettlement, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_CLEARING:
return pmodel.AccountRoleClearing, nil
return account_role.AccountRoleClearing, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_PENDING:
return pmodel.AccountRolePending, nil
return account_role.AccountRolePending, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_RESERVE:
return pmodel.AccountRoleReserve, nil
return account_role.AccountRoleReserve, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_LIQUIDITY:
return pmodel.AccountRoleLiquidity, nil
return account_role.AccountRoleLiquidity, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_FEE:
return pmodel.AccountRoleFee, nil
return account_role.AccountRoleFee, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_CHARGEBACK:
return pmodel.AccountRoleChargeback, nil
return account_role.AccountRoleChargeback, nil
case ledgerv1.AccountRole_ACCOUNT_ROLE_ADJUSTMENT:
return pmodel.AccountRoleAdjustment, nil
return account_role.AccountRoleAdjustment, nil
default:
return "", merrors.InvalidArgument("invalid account role")
}
}
func modelAccountRoleToProto(r pmodel.AccountRole) ledgerv1.AccountRole {
func modelAccountRoleToProto(r account_role.AccountRole) ledgerv1.AccountRole {
switch r {
case pmodel.AccountRoleOperating, "":
case account_role.AccountRoleOperating, "":
return ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING
case pmodel.AccountRoleHold:
case account_role.AccountRoleHold:
return ledgerv1.AccountRole_ACCOUNT_ROLE_HOLD
case pmodel.AccountRoleTransit:
case account_role.AccountRoleTransit:
return ledgerv1.AccountRole_ACCOUNT_ROLE_TRANSIT
case pmodel.AccountRoleSettlement:
case account_role.AccountRoleSettlement:
return ledgerv1.AccountRole_ACCOUNT_ROLE_SETTLEMENT
case pmodel.AccountRoleClearing:
case account_role.AccountRoleClearing:
return ledgerv1.AccountRole_ACCOUNT_ROLE_CLEARING
case pmodel.AccountRolePending:
case account_role.AccountRolePending:
return ledgerv1.AccountRole_ACCOUNT_ROLE_PENDING
case pmodel.AccountRoleReserve:
case account_role.AccountRoleReserve:
return ledgerv1.AccountRole_ACCOUNT_ROLE_RESERVE
case pmodel.AccountRoleLiquidity:
case account_role.AccountRoleLiquidity:
return ledgerv1.AccountRole_ACCOUNT_ROLE_LIQUIDITY
case pmodel.AccountRoleFee:
case account_role.AccountRoleFee:
return ledgerv1.AccountRole_ACCOUNT_ROLE_FEE
case pmodel.AccountRoleChargeback:
case account_role.AccountRoleChargeback:
return ledgerv1.AccountRole_ACCOUNT_ROLE_CHARGEBACK
case pmodel.AccountRoleAdjustment:
case account_role.AccountRoleAdjustment:
return ledgerv1.AccountRole_ACCOUNT_ROLE_ADJUSTMENT
default:
return ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED
@@ -414,7 +415,7 @@ func describableToProto(desc pmodel.Describable) *describablev1.Describable {
}
func (s *Service) ensureSettlementAccount(ctx context.Context, orgRef bson.ObjectID, currency string) (*pmodel.LedgerAccount, error) {
return s.ensureRoleAccount(ctx, orgRef, currency, pmodel.AccountRoleSettlement)
return s.ensureRoleAccount(ctx, orgRef, currency, account_role.AccountRoleSettlement)
}
func generateAccountCode(accountType pmodel.LedgerAccountType, currency string, id bson.ObjectID) string {

View File

@@ -12,6 +12,7 @@ import (
"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"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
)
@@ -21,14 +22,14 @@ type accountStoreStub struct {
created []*pmodel.LedgerAccount
existing *pmodel.LedgerAccount
existingErr error
existingByRole map[pmodel.AccountRole]*pmodel.LedgerAccount
existingByRole map[account_role.AccountRole]*pmodel.LedgerAccount
defaultSettlement *pmodel.LedgerAccount
defaultErr error
createErrs []error
}
func (s *accountStoreStub) Create(_ context.Context, account *pmodel.LedgerAccount) error {
if account.Role == pmodel.AccountRoleSettlement {
if account.Role == account_role.AccountRoleSettlement {
if s.createErrSettlement != nil {
return s.createErrSettlement
}
@@ -66,7 +67,7 @@ func (s *accountStoreStub) Get(context.Context, bson.ObjectID) (*pmodel.LedgerAc
return nil, storage.ErrAccountNotFound
}
func (s *accountStoreStub) GetByRole(_ context.Context, orgRef bson.ObjectID, currency string, role pmodel.AccountRole) (*pmodel.LedgerAccount, error) {
func (s *accountStoreStub) GetByRole(_ context.Context, orgRef bson.ObjectID, currency string, role account_role.AccountRole) (*pmodel.LedgerAccount, error) {
if s.existingByRole != nil {
if acc, ok := s.existingByRole[role]; ok {
return acc, nil
@@ -190,14 +191,14 @@ func TestCreateAccountResponder_AutoCreatesSettlementAccount(t *testing.T) {
var settlement *pmodel.LedgerAccount
var operating *pmodel.LedgerAccount
roles := make(map[pmodel.AccountRole]bool)
roles := make(map[account_role.AccountRole]bool)
for _, acc := range accountStore.created {
roles[acc.Role] = true
if acc.Role == pmodel.AccountRoleSettlement {
if acc.Role == account_role.AccountRoleSettlement {
settlement = acc
}
if acc.Role == pmodel.AccountRoleOperating {
if acc.Role == account_role.AccountRoleOperating {
operating = acc
}
@@ -230,7 +231,7 @@ func TestCreateAccountResponder_AutoCreatesSettlementAccount(t *testing.T) {
require.Equal(t, pmodel.LedgerAccountTypeAsset, settlement.AccountType)
require.Equal(t, "USD", settlement.Currency)
require.False(t, settlement.AllowNegative)
require.Equal(t, pmodel.AccountRoleSettlement, settlement.Role)
require.Equal(t, account_role.AccountRoleSettlement, settlement.Role)
require.Equal(t, "true", settlement.Metadata["system"])
}
@@ -265,7 +266,7 @@ func TestCreateAccountResponder_RetriesOnConflict(t *testing.T) {
var createdFee *pmodel.LedgerAccount
for _, acc := range accountStore.created {
if acc.Role == pmodel.AccountRoleFee {
if acc.Role == account_role.AccountRoleFee {
createdFee = acc
break
}

View File

@@ -252,7 +252,7 @@ func (c *connectorAdapter) SubmitOperation(ctx context.Context, req *connectorv1
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, accountID)}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_CONFIRMED)}, nil
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_OPERATION_SUCCESS)}, nil
case connectorv1.OperationType_DEBIT:
accountID := operationAccountID(op.GetFrom())
if accountID == "" && op.GetFromRole() == accountrolev1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
@@ -280,7 +280,7 @@ func (c *connectorAdapter) SubmitOperation(ctx context.Context, req *connectorv1
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, accountID)}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_CONFIRMED)}, nil
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_OPERATION_SUCCESS)}, nil
case connectorv1.OperationType_TRANSFER:
fromID := operationAccountID(op.GetFrom())
toID := operationAccountID(op.GetTo())
@@ -306,7 +306,7 @@ func (c *connectorAdapter) SubmitOperation(ctx context.Context, req *connectorv1
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_CONFIRMED)}, nil
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_OPERATION_SUCCESS)}, nil
case connectorv1.OperationType_FX:
fromID := operationAccountID(op.GetFrom())
toID := operationAccountID(op.GetTo())
@@ -333,7 +333,7 @@ func (c *connectorAdapter) SubmitOperation(ctx context.Context, req *connectorv1
if err != nil {
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
}
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_CONFIRMED)}, nil
return &connectorv1.SubmitOperationResponse{Receipt: ledgerReceipt(resp.GetJournalEntryRef(), connectorv1.OperationStatus_OPERATION_SUCCESS)}, nil
default:
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op, "")}}, nil
}
@@ -535,7 +535,7 @@ func ledgerEntryToOperation(entry *ledgerv1.JournalEntryResponse) *connectorv1.O
op := &connectorv1.Operation{
OperationId: strings.TrimSpace(entry.GetEntryRef()),
Type: ledgerEntryType(entry.GetEntryType()),
Status: connectorv1.OperationStatus_CONFIRMED,
Status: connectorv1.OperationStatus_OPERATION_SUCCESS,
CreatedAt: entry.GetEventTime(),
UpdatedAt: entry.GetEventTime(),
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/tech/sendico/pkg/db/transaction"
"github.com/tech/sendico/pkg/merrors"
pmodel "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/model/account_role"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -86,7 +87,7 @@ func (s *memoryAccountsStore) GetByAccountCode(context.Context, bson.ObjectID, s
return nil, merrors.NotImplemented("get by code")
}
func (s *memoryAccountsStore) GetByRole(context.Context, bson.ObjectID, string, pmodel.AccountRole) (*pmodel.LedgerAccount, error) {
func (s *memoryAccountsStore) GetByRole(context.Context, bson.ObjectID, string, account_role.AccountRole) (*pmodel.LedgerAccount, error) {
return nil, merrors.NotImplemented("get by role")
}
@@ -242,7 +243,7 @@ func newTestService() (*Service, *memoryRepository) {
return svc, repo
}
func newOrgAccount(orgRef bson.ObjectID, currency string, role pmodel.AccountRole) *pmodel.LedgerAccount {
func newOrgAccount(orgRef bson.ObjectID, currency string, role account_role.AccountRole) *pmodel.LedgerAccount {
account := &pmodel.LedgerAccount{
AccountCode: "test:" + strings.ToLower(currency) + ":" + bson.NewObjectID().Hex(),
Currency: currency,
@@ -290,7 +291,7 @@ func TestExternalCreditAndDebit(t *testing.T) {
require.NoError(t, svc.ensureSystemAccounts(ctx))
orgRef := bson.NewObjectID()
pending := newOrgAccount(orgRef, "USD", pmodel.AccountRolePending)
pending := newOrgAccount(orgRef, "USD", account_role.AccountRolePending)
require.NoError(t, repo.accounts.Create(ctx, pending))
creditResp, err := svc.PostExternalCreditWithCharges(ctx, &ledgerv1.PostCreditRequest{
@@ -334,7 +335,7 @@ func TestExternalCreditCurrencyMismatch(t *testing.T) {
require.NoError(t, svc.ensureSystemAccounts(ctx))
orgRef := bson.NewObjectID()
pending := newOrgAccount(orgRef, "USD", pmodel.AccountRolePending)
pending := newOrgAccount(orgRef, "USD", account_role.AccountRolePending)
require.NoError(t, repo.accounts.Create(ctx, pending))
source, err := repo.accounts.GetSystemAccount(ctx, pmodel.SystemAccountPurposeExternalSource, "USD")
@@ -396,8 +397,8 @@ func TestExternalFlowInvariant(t *testing.T) {
require.NoError(t, svc.ensureSystemAccounts(ctx))
orgRef := bson.NewObjectID()
pending := newOrgAccount(orgRef, "USD", pmodel.AccountRolePending)
transit := newOrgAccount(orgRef, "USD", pmodel.AccountRoleTransit)
pending := newOrgAccount(orgRef, "USD", account_role.AccountRolePending)
transit := newOrgAccount(orgRef, "USD", account_role.AccountRoleTransit)
require.NoError(t, repo.accounts.Create(ctx, pending))
require.NoError(t, repo.accounts.Create(ctx, transit))
@@ -442,8 +443,8 @@ func TestExternalInvariantRandomSequence(t *testing.T) {
require.NoError(t, svc.ensureSystemAccounts(ctx))
orgRef := bson.NewObjectID()
pending := newOrgAccount(orgRef, "USD", pmodel.AccountRolePending)
transit := newOrgAccount(orgRef, "USD", pmodel.AccountRoleTransit)
pending := newOrgAccount(orgRef, "USD", account_role.AccountRolePending)
transit := newOrgAccount(orgRef, "USD", account_role.AccountRoleTransit)
require.NoError(t, repo.accounts.Create(ctx, pending))
require.NoError(t, repo.accounts.Create(ctx, transit))

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"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"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -28,7 +29,7 @@ func (s *Service) postCreditResponder(_ context.Context, req *ledgerv1.PostCredi
if req.OrganizationRef == "" {
return nil, merrors.InvalidArgument("organization_ref is required")
}
roleModel := pmodel.AccountRole("")
roleModel := account_role.AccountRole("")
if req.Role != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
roleModel, err = protoAccountRoleToModel(req.Role)
@@ -36,7 +37,7 @@ func (s *Service) postCreditResponder(_ context.Context, req *ledgerv1.PostCredi
return nil, err
}
} else if strings.TrimSpace(req.LedgerAccountRef) == "" {
roleModel = pmodel.AccountRoleOperating
roleModel = account_role.AccountRoleOperating
}
if strings.TrimSpace(req.LedgerAccountRef) == "" && roleModel == "" {
return nil, merrors.InvalidArgument("ledger_account_ref or role is required")

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"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"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -26,7 +27,7 @@ func (s *Service) postDebitResponder(_ context.Context, req *ledgerv1.PostDebitR
if req.OrganizationRef == "" {
return nil, merrors.InvalidArgument("organization_ref is required")
}
roleModel := pmodel.AccountRole("")
roleModel := account_role.AccountRole("")
if req.Role != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
roleModel, err = protoAccountRoleToModel(req.Role)
@@ -34,7 +35,7 @@ func (s *Service) postDebitResponder(_ context.Context, req *ledgerv1.PostDebitR
return nil, err
}
} else if strings.TrimSpace(req.LedgerAccountRef) == "" {
roleModel = pmodel.AccountRoleOperating
roleModel = account_role.AccountRoleOperating
}
if strings.TrimSpace(req.LedgerAccountRef) == "" && roleModel == "" {
return nil, merrors.InvalidArgument("ledger_account_ref or role is required")

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"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"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -25,7 +26,7 @@ func (s *Service) postExternalCreditResponder(_ context.Context, req *ledgerv1.P
if req.OrganizationRef == "" {
return nil, merrors.InvalidArgument("organization_ref is required")
}
roleModel := pmodel.AccountRole("")
roleModel := account_role.AccountRole("")
if req.Role != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
roleModel, err = protoAccountRoleToModel(req.Role)
@@ -258,7 +259,7 @@ func (s *Service) postExternalDebitResponder(_ context.Context, req *ledgerv1.Po
if req.OrganizationRef == "" {
return nil, merrors.InvalidArgument("organization_ref is required")
}
roleModel := pmodel.AccountRole("")
roleModel := account_role.AccountRole("")
if req.Role != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
roleModel, err = protoAccountRoleToModel(req.Role)

View File

@@ -12,6 +12,7 @@ import (
"github.com/tech/sendico/ledger/storage/model"
"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"
@@ -33,7 +34,7 @@ type outboxJournalPayload struct {
Lines []outboxLinePayload `json:"lines"`
}
func validateAccountRole(account *pmodel.LedgerAccount, expected pmodel.AccountRole, label string) error {
func validateAccountRole(account *pmodel.LedgerAccount, expected account_role.AccountRole, label string) error {
if expected == "" {
return nil
}
@@ -47,7 +48,7 @@ func validateAccountRole(account *pmodel.LedgerAccount, expected pmodel.AccountR
// If accountRefStr is non-empty, it fetches by ID and optionally asserts the role.
// If accountRefStr is empty and role is set, it resolves via GetByRole(orgRef, currency, role).
// Returns the account and its ObjectID, or an error.
func (s *Service) resolveAccount(ctx context.Context, accountRefStr string, role pmodel.AccountRole, orgRef bson.ObjectID, currency, label string) (*pmodel.LedgerAccount, bson.ObjectID, error) {
func (s *Service) resolveAccount(ctx context.Context, accountRefStr string, role account_role.AccountRole, orgRef bson.ObjectID, currency, label string) (*pmodel.LedgerAccount, bson.ObjectID, error) {
if accountRefStr != "" {
ref, err := parseObjectID(accountRefStr)
if err != nil {

View File

@@ -13,6 +13,7 @@ import (
"github.com/tech/sendico/ledger/storage/model"
"github.com/tech/sendico/pkg/merrors"
pmodel "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/model/account_role"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -52,7 +53,7 @@ func (s *stubAccountsStore) Get(ctx context.Context, accountRef bson.ObjectID) (
func (s *stubAccountsStore) GetByAccountCode(context.Context, bson.ObjectID, string, string) (*pmodel.LedgerAccount, error) {
return nil, merrors.NotImplemented("get by code")
}
func (s *stubAccountsStore) GetByRole(context.Context, bson.ObjectID, string, pmodel.AccountRole) (*pmodel.LedgerAccount, error) {
func (s *stubAccountsStore) GetByRole(context.Context, bson.ObjectID, string, account_role.AccountRole) (*pmodel.LedgerAccount, error) {
return nil, merrors.NotImplemented("get by role")
}
func (s *stubAccountsStore) GetSystemAccount(context.Context, pmodel.SystemAccountPurpose, string) (*pmodel.LedgerAccount, error) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"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"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -27,7 +28,7 @@ func (s *Service) transferResponder(_ context.Context, req *ledgerv1.TransferReq
if req.OrganizationRef == "" {
return nil, merrors.InvalidArgument("organization_ref is required")
}
fromRoleModel := pmodel.AccountRole("")
fromRoleModel := account_role.AccountRole("")
if req.FromRole != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
fromRoleModel, err = protoAccountRoleToModel(req.FromRole)
@@ -35,9 +36,9 @@ func (s *Service) transferResponder(_ context.Context, req *ledgerv1.TransferReq
return nil, err
}
} else if strings.TrimSpace(req.FromLedgerAccountRef) == "" {
fromRoleModel = pmodel.AccountRoleOperating
fromRoleModel = account_role.AccountRoleOperating
}
toRoleModel := pmodel.AccountRole("")
toRoleModel := account_role.AccountRole("")
if req.ToRole != ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED {
var err error
toRoleModel, err = protoAccountRoleToModel(req.ToRole)
@@ -45,7 +46,7 @@ func (s *Service) transferResponder(_ context.Context, req *ledgerv1.TransferReq
return nil, err
}
} else if strings.TrimSpace(req.ToLedgerAccountRef) == "" {
toRoleModel = pmodel.AccountRoleOperating
toRoleModel = account_role.AccountRoleOperating
}
if strings.TrimSpace(req.FromLedgerAccountRef) == "" && fromRoleModel == "" {
return nil, merrors.InvalidArgument("from_ledger_account_ref or from_role is required")

View File

@@ -8,6 +8,7 @@ import (
"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"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -40,7 +41,7 @@ func (s *systemAccountsStoreStub) GetByAccountCode(context.Context, bson.ObjectI
return nil, merrors.NotImplemented("get by code")
}
func (s *systemAccountsStoreStub) GetByRole(context.Context, bson.ObjectID, string, pmodel.AccountRole) (*pmodel.LedgerAccount, error) {
func (s *systemAccountsStoreStub) GetByRole(context.Context, bson.ObjectID, string, account_role.AccountRole) (*pmodel.LedgerAccount, error) {
return nil, merrors.NotImplemented("get by role")
}
@@ -98,7 +99,7 @@ func TestEnsureSystemAccounts_CreatesAndCaches(t *testing.T) {
require.Equal(t, pmodel.LedgerAccountScopeSystem, acc.Scope)
require.True(t, acc.AllowNegative)
require.Nil(t, acc.OrganizationRef)
require.Equal(t, pmodel.AccountRole(""), acc.Role)
require.Equal(t, account_role.AccountRole(""), acc.Role)
require.NotNil(t, acc.SystemPurpose)
require.Equal(t, pmodel.LedgerAccountTypeAsset, acc.AccountType)
require.Equal(t, pmodel.LedgerAccountStatusActive, acc.Status)

View File

@@ -8,6 +8,7 @@ import (
"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"
@@ -15,15 +16,15 @@ import (
const LedgerTopologyVersion = 1
var RequiredRolesV1 = []pmodel.AccountRole{
pmodel.AccountRoleOperating,
pmodel.AccountRoleHold,
pmodel.AccountRolePending,
pmodel.AccountRoleTransit,
pmodel.AccountRoleSettlement,
var RequiredRolesV1 = []account_role.AccountRole{
account_role.AccountRoleOperating,
account_role.AccountRoleHold,
account_role.AccountRolePending,
account_role.AccountRoleTransit,
account_role.AccountRoleSettlement,
}
func isRequiredTopologyRole(role pmodel.AccountRole) bool {
func isRequiredTopologyRole(role account_role.AccountRole) bool {
for _, required := range RequiredRolesV1 {
if role == required {
return true
@@ -52,7 +53,7 @@ func (s *Service) ensureLedgerTopology(ctx context.Context, orgRef bson.ObjectID
return nil
}
func (s *Service) ensureRoleAccount(ctx context.Context, orgRef bson.ObjectID, currency string, role pmodel.AccountRole) (*pmodel.LedgerAccount, error) {
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
}
@@ -104,7 +105,7 @@ func (s *Service) ensureRoleAccount(ctx context.Context, orgRef bson.ObjectID, c
return account, nil
}
func newSystemAccount(orgRef bson.ObjectID, currency string, role pmodel.AccountRole) *pmodel.LedgerAccount {
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),