+ ledger account open endpoint
This commit is contained in:
48
api/server/interface/api/srequest/ledger.go
Normal file
48
api/server/interface/api/srequest/ledger.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package srequest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
)
|
||||
|
||||
type LedgerAccountType string
|
||||
|
||||
const (
|
||||
LedgerAccountTypeUnspecified LedgerAccountType = "unspecified"
|
||||
LedgerAccountTypeAsset LedgerAccountType = "asset"
|
||||
LedgerAccountTypeLiability LedgerAccountType = "liability"
|
||||
LedgerAccountTypeRevenue LedgerAccountType = "revenue"
|
||||
LedgerAccountTypeExpense LedgerAccountType = "expense"
|
||||
)
|
||||
|
||||
type LedgerAccountStatus string
|
||||
|
||||
const (
|
||||
LedgerAccountStatusUnspecified LedgerAccountStatus = "unspecified"
|
||||
LedgerAccountStatusActive LedgerAccountStatus = "active"
|
||||
LedgerAccountStatusFrozen LedgerAccountStatus = "frozen"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
if strings.TrimSpace(string(r.AccountType)) == "" || strings.EqualFold(string(r.AccountType), string(LedgerAccountTypeUnspecified)) {
|
||||
return merrors.InvalidArgument("accountType is required", "accountType")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -29,6 +29,11 @@ type ledgerAccountsResponse struct {
|
||||
Accounts []ledgerAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
type ledgerAccountResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Account ledgerAccount `json:"account"`
|
||||
}
|
||||
|
||||
type ledgerMoney struct {
|
||||
Amount string `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
@@ -57,6 +62,13 @@ func LedgerAccounts(logger mlogger.Logger, accounts []*ledgerv1.LedgerAccount, a
|
||||
})
|
||||
}
|
||||
|
||||
func LedgerAccountCreated(logger mlogger.Logger, account *ledgerv1.LedgerAccount, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Created(logger, ledgerAccountResponse{
|
||||
Account: toLedgerAccount(account),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func LedgerBalance(logger mlogger.Logger, resp *ledgerv1.BalanceResponse, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, ledgerBalanceResponse{
|
||||
Balance: toLedgerBalance(resp),
|
||||
|
||||
122
api/server/internal/server/ledgerapiimp/create.go
Normal file
122
api/server/internal/server/ledgerapiimp/create.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package ledgerapiimp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for ledger account create", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
allowed, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionCreate)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check ledger accounts access permissions", zap.Error(err), mutil.PLog(a.oph, r))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !allowed {
|
||||
a.logger.Debug("Access denied when creating ledger account", mutil.PLog(a.oph, r))
|
||||
return response.AccessDenied(a.logger, a.Name(), "ledger accounts write permission denied")
|
||||
}
|
||||
|
||||
payload, err := decodeLedgerAccountCreatePayload(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to decode ledger account create payload", zap.Error(err), mutil.PLog(a.oph, r))
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
accountType, err := mapLedgerAccountType(payload.AccountType)
|
||||
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, errors.New("ledger client is not configured"))
|
||||
}
|
||||
|
||||
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
AccountCode: payload.AccountCode,
|
||||
AccountType: accountType,
|
||||
Currency: payload.Currency,
|
||||
Status: status,
|
||||
AllowNegative: payload.AllowNegative,
|
||||
IsSettlement: payload.IsSettlement,
|
||||
Metadata: payload.Metadata,
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create ledger account", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||
return response.Auto(a.logger, mservice.Ledger, err)
|
||||
}
|
||||
|
||||
return sresponse.LedgerAccountCreated(a.logger, resp.GetAccount(), token)
|
||||
}
|
||||
|
||||
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
||||
defer r.Body.Close()
|
||||
|
||||
payload := &srequest.CreateLedgerAccount{}
|
||||
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))
|
||||
if len(payload.Metadata) == 0 {
|
||||
payload.Metadata = nil
|
||||
}
|
||||
if err := payload.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
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":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("accountType is required", "accountType")
|
||||
default:
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("unsupported accountType: "+string(accountType), "accountType")
|
||||
}
|
||||
}
|
||||
|
||||
func mapLedgerAccountStatus(status srequest.LedgerAccountStatus) (ledgerv1.AccountStatus, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(string(status))) {
|
||||
case "", "ACCOUNT_STATUS_UNSPECIFIED", "UNSPECIFIED":
|
||||
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:
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, merrors.InvalidArgument("unsupported status: "+string(status), "status")
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
type ledgerClient interface {
|
||||
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error)
|
||||
Close() error
|
||||
@@ -75,6 +76,7 @@ func CreateAPI(apiCtx eapi.API) (*LedgerAPI, error) {
|
||||
}
|
||||
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listAccounts)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Post, p.createAccount)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.aph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getBalance)
|
||||
|
||||
return p, nil
|
||||
|
||||
Reference in New Issue
Block a user