accounts creation
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
This commit is contained in:
@@ -7,12 +7,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/ledgerconv"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/payments/rail"
|
||||
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -192,7 +193,6 @@ func (c *ledgerClient) CreateAccount(ctx context.Context, req *ledgerv1.CreateAc
|
||||
}
|
||||
params := map[string]interface{}{
|
||||
"organization_ref": strings.TrimSpace(req.GetOrganizationRef()),
|
||||
"account_code": strings.TrimSpace(req.GetAccountCode()),
|
||||
"account_type": req.GetAccountType().String(),
|
||||
"status": req.GetStatus().String(),
|
||||
"allow_negative": req.GetAllowNegative(),
|
||||
@@ -523,29 +523,13 @@ func ledgerAccountFromConnector(account *connectorv1.Account) *ledgerv1.LedgerAc
|
||||
}
|
||||
|
||||
func parseAccountType(value string) ledgerv1.AccountType {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "ACCOUNT_TYPE_ASSET", "ASSET":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_ASSET
|
||||
case "ACCOUNT_TYPE_LIABILITY", "LIABILITY":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY
|
||||
case "ACCOUNT_TYPE_REVENUE", "REVENUE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_REVENUE
|
||||
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE
|
||||
default:
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED
|
||||
}
|
||||
accountType, _ := ledgerconv.ParseAccountType(value)
|
||||
return accountType
|
||||
}
|
||||
|
||||
func parseAccountStatus(value string) ledgerv1.AccountStatus {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "ACCOUNT_STATUS_ACTIVE", "ACTIVE":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE
|
||||
case "ACCOUNT_STATUS_FROZEN", "FROZEN":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_FROZEN
|
||||
default:
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED
|
||||
}
|
||||
status, _ := ledgerconv.ParseAccountStatus(value)
|
||||
return status
|
||||
}
|
||||
|
||||
func journalEntryFromOperation(op *connectorv1.Operation) *ledgerv1.JournalEntryResponse {
|
||||
@@ -553,11 +537,11 @@ func journalEntryFromOperation(op *connectorv1.Operation) *ledgerv1.JournalEntry
|
||||
return nil
|
||||
}
|
||||
entry := &ledgerv1.JournalEntryResponse{
|
||||
EntryRef: strings.TrimSpace(op.GetOperationId()),
|
||||
EntryType: entryTypeFromOperation(op.GetType()),
|
||||
Description: operationDescription(op),
|
||||
EventTime: op.GetCreatedAt(),
|
||||
Lines: postingLinesFromOperation(op),
|
||||
EntryRef: strings.TrimSpace(op.GetOperationId()),
|
||||
EntryType: entryTypeFromOperation(op.GetType()),
|
||||
Description: operationDescription(op),
|
||||
EventTime: op.GetCreatedAt(),
|
||||
Lines: postingLinesFromOperation(op),
|
||||
LedgerAccountRefs: ledgerAccountRefsFromOperation(op),
|
||||
}
|
||||
return entry
|
||||
|
||||
@@ -37,12 +37,6 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountCode := strings.TrimSpace(req.GetAccountCode())
|
||||
if accountCode == "" {
|
||||
return nil, merrors.InvalidArgument("account_code is required")
|
||||
}
|
||||
accountCode = strings.ToLower(accountCode)
|
||||
|
||||
currency := strings.TrimSpace(req.GetCurrency())
|
||||
if currency == "" {
|
||||
return nil, merrors.InvalidArgument("currency is required")
|
||||
@@ -85,38 +79,37 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
|
||||
ownerRef = &ownerObjID
|
||||
}
|
||||
|
||||
account := &model.Account{
|
||||
AccountCode: accountCode,
|
||||
Currency: currency,
|
||||
AccountType: modelType,
|
||||
Status: modelStatus,
|
||||
AllowNegative: req.GetAllowNegative(),
|
||||
IsSettlement: req.GetIsSettlement(),
|
||||
Metadata: metadata,
|
||||
OwnerRef: ownerRef,
|
||||
}
|
||||
if describable != nil {
|
||||
account.Describable = *describable
|
||||
}
|
||||
account.OrganizationRef = orgRef
|
||||
const maxCreateAttempts = 3
|
||||
var account *model.Account
|
||||
for attempt := 0; attempt < maxCreateAttempts; attempt++ {
|
||||
accountID := primitive.NewObjectID()
|
||||
accountCode := generateAccountCode(modelType, currency, accountID)
|
||||
account = &model.Account{
|
||||
AccountCode: accountCode,
|
||||
Currency: currency,
|
||||
AccountType: modelType,
|
||||
Status: modelStatus,
|
||||
AllowNegative: req.GetAllowNegative(),
|
||||
IsSettlement: req.GetIsSettlement(),
|
||||
Metadata: metadata,
|
||||
OwnerRef: ownerRef,
|
||||
}
|
||||
if describable != nil {
|
||||
account.Describable = *describable
|
||||
}
|
||||
account.OrganizationRef = orgRef
|
||||
account.SetID(accountID)
|
||||
|
||||
err = s.storage.Accounts().Create(ctx, account)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
existing, lookupErr := s.storage.Accounts().GetByAccountCode(ctx, orgRef, accountCode, currency)
|
||||
if lookupErr != nil {
|
||||
s.logger.Warn("duplicate account create but failed to load existing",
|
||||
zap.Error(lookupErr),
|
||||
mzap.ObjRef("organization_ref", orgRef),
|
||||
zap.String("accountCode", accountCode),
|
||||
zap.String("currency", currency))
|
||||
return nil, merrors.Internal("failed to load existing account after conflict")
|
||||
}
|
||||
recordAccountOperation("create", "duplicate")
|
||||
err = s.storage.Accounts().Create(ctx, account)
|
||||
if err == nil {
|
||||
recordAccountOperation("create", "success")
|
||||
return &ledgerv1.CreateAccountResponse{
|
||||
Account: toProtoAccount(existing),
|
||||
Account: toProtoAccount(account),
|
||||
}, nil
|
||||
}
|
||||
if errors.Is(err, merrors.ErrDataConflict) && attempt < maxCreateAttempts-1 {
|
||||
continue
|
||||
}
|
||||
recordAccountOperation("create", "error")
|
||||
s.logger.Warn("failed to create account",
|
||||
zap.Error(err),
|
||||
@@ -125,11 +118,8 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
|
||||
zap.String("currency", currency))
|
||||
return nil, merrors.Internal("failed to create account")
|
||||
}
|
||||
|
||||
recordAccountOperation("create", "success")
|
||||
return &ledgerv1.CreateAccountResponse{
|
||||
Account: toProtoAccount(account),
|
||||
}, nil
|
||||
recordAccountOperation("create", "error")
|
||||
return nil, merrors.Internal("failed to create account")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,3 +339,15 @@ func defaultSettlementAccountCode(currency string) string {
|
||||
}
|
||||
return fmt.Sprintf("asset:settlement:%s", cleaned)
|
||||
}
|
||||
|
||||
func generateAccountCode(accountType model.AccountType, currency string, id primitive.ObjectID) string {
|
||||
typePart := strings.ToLower(strings.TrimSpace(string(accountType)))
|
||||
if typePart == "" {
|
||||
typePart = "account"
|
||||
}
|
||||
currencyPart := strings.ToLower(strings.TrimSpace(currency))
|
||||
if currencyPart == "" {
|
||||
currencyPart = "na"
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", typePart, currencyPart, id.Hex())
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package ledger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
@@ -23,6 +23,7 @@ type accountStoreStub struct {
|
||||
existingErr error
|
||||
defaultSettlement *model.Account
|
||||
defaultErr error
|
||||
createErrs []error
|
||||
}
|
||||
|
||||
func (s *accountStoreStub) Create(_ context.Context, account *model.Account) error {
|
||||
@@ -30,8 +31,16 @@ func (s *accountStoreStub) Create(_ context.Context, account *model.Account) err
|
||||
if s.createErrSettlement != nil {
|
||||
return s.createErrSettlement
|
||||
}
|
||||
} else if s.createErr != nil {
|
||||
return s.createErr
|
||||
} else {
|
||||
if len(s.createErrs) > 0 {
|
||||
err := s.createErrs[0]
|
||||
s.createErrs = s.createErrs[1:]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if s.createErr != nil {
|
||||
return s.createErr
|
||||
}
|
||||
}
|
||||
if account.GetID() == nil || account.GetID().IsZero() {
|
||||
account.SetID(primitive.NewObjectID())
|
||||
@@ -94,7 +103,6 @@ func TestCreateAccountResponder_Success(t *testing.T) {
|
||||
|
||||
req := &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
AccountCode: "asset:cash:main",
|
||||
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET,
|
||||
Currency: "usd",
|
||||
AllowNegative: false,
|
||||
@@ -107,7 +115,11 @@ func TestCreateAccountResponder_Success(t *testing.T) {
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Account)
|
||||
|
||||
require.Equal(t, "asset:cash:main", resp.Account.AccountCode)
|
||||
parts := strings.Split(resp.Account.AccountCode, ":")
|
||||
require.Len(t, parts, 3)
|
||||
require.Equal(t, "asset", parts[0])
|
||||
require.Equal(t, "usd", parts[1])
|
||||
require.Len(t, parts[2], 24)
|
||||
require.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, resp.Account.AccountType)
|
||||
require.Equal(t, "USD", resp.Account.Currency)
|
||||
require.True(t, resp.Account.IsSettlement)
|
||||
@@ -129,7 +141,6 @@ func TestCreateAccountResponder_AutoCreatesSettlementAccount(t *testing.T) {
|
||||
|
||||
req := &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
AccountCode: "liability:customer:1",
|
||||
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY,
|
||||
Currency: "usd",
|
||||
}
|
||||
@@ -146,40 +157,29 @@ func TestCreateAccountResponder_AutoCreatesSettlementAccount(t *testing.T) {
|
||||
if acc.IsSettlement {
|
||||
settlement = acc
|
||||
}
|
||||
if acc.AccountCode == "liability:customer:1" {
|
||||
if !acc.IsSettlement {
|
||||
created = acc
|
||||
}
|
||||
}
|
||||
require.NotNil(t, settlement)
|
||||
require.NotNil(t, created)
|
||||
parts := strings.Split(created.AccountCode, ":")
|
||||
require.Len(t, parts, 3)
|
||||
require.Equal(t, "liability", parts[0])
|
||||
require.Equal(t, "usd", parts[1])
|
||||
require.Len(t, parts[2], 24)
|
||||
require.Equal(t, defaultSettlementAccountCode("USD"), settlement.AccountCode)
|
||||
require.Equal(t, model.AccountTypeAsset, settlement.AccountType)
|
||||
require.Equal(t, "USD", settlement.Currency)
|
||||
require.True(t, settlement.AllowNegative)
|
||||
}
|
||||
|
||||
func TestCreateAccountResponder_DuplicateReturnsExisting(t *testing.T) {
|
||||
func TestCreateAccountResponder_RetriesOnConflict(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
orgRef := primitive.NewObjectID()
|
||||
existing := &model.Account{
|
||||
AccountCode: "asset:cash:main",
|
||||
Currency: "USD",
|
||||
AccountType: model.AccountTypeAsset,
|
||||
Status: model.AccountStatusActive,
|
||||
AllowNegative: false,
|
||||
IsSettlement: true,
|
||||
Metadata: map[string]string{"purpose": "existing"},
|
||||
}
|
||||
existing.OrganizationRef = orgRef
|
||||
existing.SetID(primitive.NewObjectID())
|
||||
existing.CreatedAt = time.Now().Add(-time.Hour).UTC()
|
||||
existing.UpdatedAt = time.Now().UTC()
|
||||
|
||||
accountStore := &accountStoreStub{
|
||||
createErr: merrors.DataConflict("duplicate"),
|
||||
existing: existing,
|
||||
existingErr: nil,
|
||||
createErrs: []error{merrors.DataConflict("duplicate")},
|
||||
}
|
||||
|
||||
svc := &Service{
|
||||
@@ -189,7 +189,6 @@ func TestCreateAccountResponder_DuplicateReturnsExisting(t *testing.T) {
|
||||
|
||||
req := &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
AccountCode: "asset:cash:main",
|
||||
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET,
|
||||
Currency: "usd",
|
||||
}
|
||||
@@ -199,8 +198,15 @@ func TestCreateAccountResponder_DuplicateReturnsExisting(t *testing.T) {
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Account)
|
||||
|
||||
require.Equal(t, existing.GetID().Hex(), resp.Account.LedgerAccountRef)
|
||||
require.Equal(t, existing.Metadata["purpose"], resp.Account.Metadata["purpose"])
|
||||
require.Len(t, accountStore.created, 2)
|
||||
var created *model.Account
|
||||
for _, acc := range accountStore.created {
|
||||
if !acc.IsSettlement {
|
||||
created = acc
|
||||
}
|
||||
}
|
||||
require.NotNil(t, created)
|
||||
require.Equal(t, created.AccountCode, resp.Account.AccountCode)
|
||||
}
|
||||
|
||||
func TestCreateAccountResponder_InvalidAccountType(t *testing.T) {
|
||||
@@ -213,7 +219,6 @@ func TestCreateAccountResponder_InvalidAccountType(t *testing.T) {
|
||||
|
||||
req := &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: primitive.NewObjectID().Hex(),
|
||||
AccountCode: "asset:cash:main",
|
||||
Currency: "USD",
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/tech/sendico/ledger/internal/appversion"
|
||||
"github.com/tech/sendico/pkg/connector/params"
|
||||
"github.com/tech/sendico/pkg/ledgerconv"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
@@ -57,9 +58,8 @@ func (c *connectorAdapter) OpenAccount(ctx context.Context, req *connectorv1.Ope
|
||||
|
||||
reader := params.New(req.GetParams())
|
||||
orgRef := strings.TrimSpace(reader.String("organization_ref"))
|
||||
accountCode := strings.TrimSpace(reader.String("account_code"))
|
||||
if orgRef == "" || accountCode == "" {
|
||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: organization_ref and account_code are required", nil, "")}, nil
|
||||
if orgRef == "" {
|
||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: organization_ref is required", nil, "")}, nil
|
||||
}
|
||||
|
||||
accountType, err := parseLedgerAccountType(reader, "account_type")
|
||||
@@ -81,7 +81,6 @@ func (c *connectorAdapter) OpenAccount(ctx context.Context, req *connectorv1.Ope
|
||||
|
||||
resp, err := c.svc.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef,
|
||||
AccountCode: accountCode,
|
||||
AccountType: accountType,
|
||||
Currency: currency,
|
||||
Status: status,
|
||||
@@ -312,7 +311,6 @@ func (c *connectorAdapter) ListOperations(ctx context.Context, req *connectorv1.
|
||||
func ledgerOpenAccountParams() []*connectorv1.ParamSpec {
|
||||
return []*connectorv1.ParamSpec{
|
||||
{Key: "organization_ref", Type: connectorv1.ParamType_STRING, Required: true, Description: "Organization reference for the ledger account."},
|
||||
{Key: "account_code", Type: connectorv1.ParamType_STRING, Required: true, Description: "Ledger account code."},
|
||||
{Key: "account_type", Type: connectorv1.ParamType_STRING, Required: true, Description: "ASSET | LIABILITY | REVENUE | EXPENSE."},
|
||||
{Key: "status", Type: connectorv1.ParamType_STRING, Required: false, Description: "ACTIVE | FROZEN."},
|
||||
{Key: "allow_negative", Type: connectorv1.ParamType_BOOL, Required: false, Description: "Allow negative balance."},
|
||||
@@ -550,30 +548,16 @@ func parseLedgerAccountType(reader params.Reader, key string) (ledgerv1.AccountT
|
||||
}
|
||||
|
||||
func parseLedgerAccountTypeString(value string) (ledgerv1.AccountType, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "ACCOUNT_TYPE_ASSET", "ASSET":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, nil
|
||||
case "ACCOUNT_TYPE_LIABILITY", "LIABILITY":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY, nil
|
||||
case "ACCOUNT_TYPE_REVENUE", "REVENUE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_REVENUE, nil
|
||||
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE, nil
|
||||
default:
|
||||
accountType, ok := ledgerconv.ParseAccountType(value)
|
||||
if !ok || ledgerconv.IsAccountTypeUnspecified(value) {
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("open_account: invalid account_type")
|
||||
}
|
||||
return accountType, nil
|
||||
}
|
||||
|
||||
func parseLedgerAccountStatus(reader params.Reader, key string) ledgerv1.AccountStatus {
|
||||
value := strings.ToUpper(strings.TrimSpace(reader.String(key)))
|
||||
switch value {
|
||||
case "ACCOUNT_STATUS_ACTIVE", "ACTIVE":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE
|
||||
case "ACCOUNT_STATUS_FROZEN", "FROZEN":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_FROZEN
|
||||
default:
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED
|
||||
}
|
||||
status, _ := ledgerconv.ParseAccountStatus(reader.String(key))
|
||||
return status
|
||||
}
|
||||
|
||||
func parseEventTime(reader params.Reader) *timestamppb.Timestamp {
|
||||
|
||||
55
api/pkg/ledgerconv/account.go
Normal file
55
api/pkg/ledgerconv/account.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package ledgerconv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
)
|
||||
|
||||
func ParseAccountType(value string) (ledgerv1.AccountType, bool) {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "ACCOUNT_TYPE_ASSET", "ASSET":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, true
|
||||
case "ACCOUNT_TYPE_LIABILITY", "LIABILITY":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY, true
|
||||
case "ACCOUNT_TYPE_REVENUE", "REVENUE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_REVENUE, true
|
||||
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE, true
|
||||
case "ACCOUNT_TYPE_UNSPECIFIED", "UNSPECIFIED":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, true
|
||||
default:
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, false
|
||||
}
|
||||
}
|
||||
|
||||
func ParseAccountStatus(value string) (ledgerv1.AccountStatus, bool) {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "ACCOUNT_STATUS_ACTIVE", "ACTIVE":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, true
|
||||
case "ACCOUNT_STATUS_FROZEN", "FROZEN":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_FROZEN, true
|
||||
case "ACCOUNT_STATUS_UNSPECIFIED", "UNSPECIFIED":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, true
|
||||
default:
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, false
|
||||
}
|
||||
}
|
||||
|
||||
func IsAccountTypeUnspecified(value string) bool {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "", "ACCOUNT_TYPE_UNSPECIFIED", "UNSPECIFIED":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func IsAccountStatusUnspecified(value string) bool {
|
||||
switch strings.ToUpper(strings.TrimSpace(value)) {
|
||||
case "", "ACCOUNT_STATUS_UNSPECIFIED", "UNSPECIFIED":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,8 @@ message PostingLine {
|
||||
message CreateAccountRequest {
|
||||
string organization_ref = 1;
|
||||
string owner_ref = 2;
|
||||
string account_code = 3;
|
||||
reserved 3;
|
||||
reserved "account_code";
|
||||
AccountType account_type = 4;
|
||||
string currency = 5;
|
||||
AccountStatus status = 6;
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type LedgerAccountType string
|
||||
@@ -26,21 +27,16 @@ const (
|
||||
)
|
||||
|
||||
type CreateLedgerAccount struct {
|
||||
AccountCode string `json:"accountCode"`
|
||||
AccountType LedgerAccountType `json:"accountType"`
|
||||
Currency string `json:"currency"`
|
||||
Status LedgerAccountStatus `json:"status,omitempty"`
|
||||
AllowNegative bool `json:"allowNegative,omitempty"`
|
||||
IsSettlement bool `json:"isSettlement,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Describable model.Describable `json:"describable"`
|
||||
IsOrgWallet bool `json:"isOrgWallet"`
|
||||
OwnerRef *primitive.ObjectID `json:"ownerRef,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (r *CreateLedgerAccount) Validate() error {
|
||||
if strings.TrimSpace(r.AccountCode) == "" {
|
||||
return merrors.InvalidArgument("accountCode is required", "accountCode")
|
||||
}
|
||||
if strings.TrimSpace(r.Currency) == "" {
|
||||
return merrors.InvalidArgument("currency is required", "currency")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package srequest
|
||||
|
||||
import "github.com/tech/sendico/pkg/model"
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type CreateWallet struct {
|
||||
Description model.Describable `json:"description"`
|
||||
IsOrgWallet bool `json:"isOrgWallet"`
|
||||
Asset model.ChainAssetKey `json:"asset"`
|
||||
OwnerRef *primitive.ObjectID `json:"ownerRef,omitempty"`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/ledgerconv"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -46,11 +47,6 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
||||
if err != nil {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
status, err := mapLedgerAccountStatus(payload.Status)
|
||||
if err != nil {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if a.client == nil {
|
||||
return response.Internal(a.logger, mservice.Ledger, merrors.Internal("ledger client is not configured"))
|
||||
}
|
||||
@@ -71,17 +67,16 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
||||
}
|
||||
}
|
||||
var ownerRef string
|
||||
if !payload.IsOrgWallet {
|
||||
ownerRef = account.ID.Hex()
|
||||
if payload.OwnerRef != nil && !payload.OwnerRef.IsZero() {
|
||||
ownerRef = payload.OwnerRef.Hex()
|
||||
}
|
||||
|
||||
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
OwnerRef: ownerRef,
|
||||
AccountCode: payload.AccountCode,
|
||||
AccountType: accountType,
|
||||
Currency: payload.Currency,
|
||||
Status: status,
|
||||
Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE,
|
||||
AllowNegative: payload.AllowNegative,
|
||||
IsSettlement: payload.IsSettlement,
|
||||
Metadata: payload.Metadata,
|
||||
@@ -102,7 +97,6 @@ func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAc
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
||||
}
|
||||
payload.AccountCode = strings.TrimSpace(payload.AccountCode)
|
||||
payload.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency))
|
||||
payload.Describable.Name = strings.TrimSpace(payload.Describable.Name)
|
||||
if payload.Describable.Description != nil {
|
||||
@@ -123,31 +117,25 @@ func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAc
|
||||
}
|
||||
|
||||
func mapLedgerAccountType(accountType srequest.LedgerAccountType) (ledgerv1.AccountType, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(string(accountType))) {
|
||||
case "ACCOUNT_TYPE_ASSET", "ASSET":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, nil
|
||||
case "ACCOUNT_TYPE_LIABILITY", "LIABILITY":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY, nil
|
||||
case "ACCOUNT_TYPE_REVENUE", "REVENUE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_REVENUE, nil
|
||||
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE, nil
|
||||
case "", "ACCOUNT_TYPE_UNSPECIFIED", "UNSPECIFIED":
|
||||
raw := string(accountType)
|
||||
if ledgerconv.IsAccountTypeUnspecified(raw) {
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("accountType is required", "accountType")
|
||||
default:
|
||||
}
|
||||
parsed, ok := ledgerconv.ParseAccountType(raw)
|
||||
if !ok {
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("unsupported accountType: "+string(accountType), "accountType")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func mapLedgerAccountStatus(status srequest.LedgerAccountStatus) (ledgerv1.AccountStatus, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(string(status))) {
|
||||
case "", "ACCOUNT_STATUS_UNSPECIFIED", "UNSPECIFIED":
|
||||
raw := string(status)
|
||||
if ledgerconv.IsAccountStatusUnspecified(raw) {
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, nil
|
||||
case "ACCOUNT_STATUS_ACTIVE", "ACTIVE":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, nil
|
||||
case "ACCOUNT_STATUS_FROZEN", "FROZEN":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_FROZEN, nil
|
||||
default:
|
||||
}
|
||||
parsed, ok := ledgerconv.ParseAccountStatus(raw)
|
||||
if !ok {
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, merrors.InvalidArgument("unsupported status: "+string(status), "status")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ func (a *WalletAPI) create(r *http.Request, account *model.Account, token *sresp
|
||||
}
|
||||
|
||||
var ownerRef string
|
||||
if !sr.IsOrgWallet {
|
||||
ownerRef = account.ID.Hex()
|
||||
if sr.OwnerRef != nil && !sr.OwnerRef.IsZero() {
|
||||
ownerRef = sr.OwnerRef.Hex()
|
||||
}
|
||||
passet, err := ast.Asset2Proto(&asset.Asset)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user