package ledgerapiimp import ( "encoding/json" "net/http" "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/model/account_role" "github.com/tech/sendico/pkg/mservice" describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" 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/v2/bson" "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, bson.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) } accountRole, err := mapLedgerAccountRole(payload.Role) 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")) } var describable *describablev1.Describable name := strings.TrimSpace(payload.Describable.Name) var description *string if payload.Describable.Description != nil { trimmed := strings.TrimSpace(*payload.Describable.Description) if trimmed != "" { description = &trimmed } } if name != "" || description != nil { describable = &describablev1.Describable{ Name: name, Description: description, } } var ownerRef string if payload.OwnerRef != nil && !payload.OwnerRef.IsZero() { ownerRef = payload.OwnerRef.Hex() } resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{ OrganizationRef: orgRef.Hex(), OwnerRef: ownerRef, AccountType: accountType, Currency: payload.Currency, Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, AllowNegative: payload.AllowNegative, Role: accountRole, Metadata: payload.Metadata, Describable: describable, }) 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.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency)) payload.Describable.Name = strings.TrimSpace(payload.Describable.Name) if payload.Describable.Description != nil { trimmed := strings.TrimSpace(*payload.Describable.Description) if trimmed == "" { payload.Describable.Description = nil } else { payload.Describable.Description = &trimmed } } 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) { raw := string(accountType) if ledgerconv.IsAccountTypeUnspecified(raw) { return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("accountType is required", "accountType") } parsed, ok := ledgerconv.ParseAccountType(raw) if !ok { return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("unsupported accountType: "+string(accountType), "accountType") } return parsed, nil } func mapLedgerAccountRole(role account_role.AccountRole) (ledgerv1.AccountRole, error) { raw := strings.TrimSpace(string(role)) if ledgerconv.IsAccountRoleUnspecified(raw) { return ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, nil } parsed, ok := ledgerconv.ParseAccountRole(raw) if !ok { return ledgerv1.AccountRole_ACCOUNT_ROLE_UNSPECIFIED, merrors.InvalidArgument("unsupported role: "+raw, "role") } return parsed, nil }