ledger account describibale support

This commit is contained in:
Stephan D
2026-01-06 17:51:35 +01:00
parent 12700c5595
commit 43edbc109d
34 changed files with 326 additions and 91 deletions

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/ledger/storage/model"
"github.com/tech/sendico/pkg/api/routers/gsresponse"
"github.com/tech/sendico/pkg/merrors"
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -62,6 +63,8 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
metadata = nil
}
describable := describableFromProto(req.GetDescribable())
account := &model.Account{
AccountCode: accountCode,
Currency: currency,
@@ -71,6 +74,9 @@ func (s *Service) createAccountResponder(_ context.Context, req *ledgerv1.Create
IsSettlement: req.GetIsSettlement(),
Metadata: metadata,
}
if describable != nil {
account.Describable = *describable
}
account.OrganizationRef = orgRef
err = s.storage.Accounts().Create(ctx, account)
@@ -204,5 +210,45 @@ func toProtoAccount(account *model.Account) *ledgerv1.LedgerAccount {
Metadata: metadata,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
Describable: describableToProto(account.Describable),
}
}
func describableFromProto(desc *describablev1.Describable) *model.Describable {
if desc == nil {
return nil
}
name := strings.TrimSpace(desc.GetName())
var description *string
if desc.Description != nil {
trimmed := strings.TrimSpace(desc.GetDescription())
if trimmed != "" {
description = &trimmed
}
}
if name == "" && description == nil {
return nil
}
return &model.Describable{
Name: name,
Description: description,
}
}
func describableToProto(desc model.Describable) *describablev1.Describable {
name := strings.TrimSpace(desc.Name)
var description *string
if desc.Description != nil {
trimmed := strings.TrimSpace(*desc.Description)
if trimmed != "" {
description = &trimmed
}
}
if name == "" && description == nil {
return nil
}
return &describablev1.Describable{
Name: name,
Description: description,
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/tech/sendico/ledger/internal/appversion"
"github.com/tech/sendico/pkg/connector/params"
"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"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
@@ -76,6 +77,7 @@ func (c *connectorAdapter) OpenAccount(ctx context.Context, req *connectorv1.Ope
status := parseLedgerAccountStatus(reader, "status")
metadata := mergeMetadata(reader.StringMap("metadata"), req.GetLabel(), req.GetOwnerRef(), req.GetCorrelationId(), req.GetParentIntentId())
describable := describableFromLabel(req.GetLabel(), reader.String("description"))
resp, err := c.svc.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
OrganizationRef: orgRef,
@@ -86,6 +88,7 @@ func (c *connectorAdapter) OpenAccount(ctx context.Context, req *connectorv1.Ope
AllowNegative: reader.Bool("allow_negative"),
IsSettlement: reader.Bool("is_settlement"),
Metadata: metadata,
Describable: describable,
})
if err != nil {
return &connectorv1.OpenAccountResponse{Error: connectorError(mapErrorCode(err), err.Error(), nil, "")}, nil
@@ -340,6 +343,7 @@ func ledgerAccountToConnector(account *ledgerv1.LedgerAccount) *connectorv1.Acco
"allow_negative": account.GetAllowNegative(),
"is_settlement": account.GetIsSettlement(),
})
describable := ledgerAccountDescribable(account)
return &connectorv1.Account{
Ref: &connectorv1.AccountRef{
ConnectorId: ledgerConnectorID,
@@ -353,6 +357,7 @@ func ledgerAccountToConnector(account *ledgerv1.LedgerAccount) *connectorv1.Acco
ProviderDetails: details,
CreatedAt: account.GetCreatedAt(),
UpdatedAt: account.GetUpdatedAt(),
Describable: describable,
}
}
@@ -367,6 +372,71 @@ func ledgerAccountState(status ledgerv1.AccountStatus) connectorv1.AccountState
}
}
func ledgerAccountDescribable(account *ledgerv1.LedgerAccount) *describablev1.Describable {
if account == nil {
return nil
}
if desc := cleanedDescribable(account.GetDescribable()); desc != nil {
return desc
}
metadata := account.GetMetadata()
name := ""
if metadata != nil {
if v := strings.TrimSpace(metadata["name"]); v != "" {
name = v
} else if v := strings.TrimSpace(metadata["label"]); v != "" {
name = v
}
}
if name == "" {
name = strings.TrimSpace(account.GetAccountCode())
}
desc := ""
if metadata != nil {
desc = strings.TrimSpace(metadata["description"])
}
if name == "" && desc == "" {
return nil
}
if desc == "" {
return &describablev1.Describable{Name: name}
}
return &describablev1.Describable{Name: name, Description: &desc}
}
func describableFromLabel(label, description string) *describablev1.Describable {
label = strings.TrimSpace(label)
description = strings.TrimSpace(description)
if label == "" && description == "" {
return nil
}
if description == "" {
return &describablev1.Describable{Name: label}
}
return &describablev1.Describable{Name: label, Description: &description}
}
func cleanedDescribable(desc *describablev1.Describable) *describablev1.Describable {
if desc == nil {
return nil
}
name := strings.TrimSpace(desc.GetName())
var description *string
if desc.Description != nil {
trimmed := strings.TrimSpace(desc.GetDescription())
if trimmed != "" {
description = &trimmed
}
}
if name == "" && description == nil {
return nil
}
return &describablev1.Describable{
Name: name,
Description: description,
}
}
func ledgerReceipt(ref string, status connectorv1.OperationStatus) *connectorv1.OperationReceipt {
return &connectorv1.OperationReceipt{
OperationId: strings.TrimSpace(ref),
@@ -454,7 +524,7 @@ func operationAccountID(party *connectorv1.OperationParty) string {
func parseLedgerAccountType(reader params.Reader, key string) (ledgerv1.AccountType, error) {
value, ok := reader.Value(key)
if !ok {
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, fmt.Errorf("open_account: account_type is required")
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("open_account: account_type is required")
}
switch v := value.(type) {
case string:
@@ -466,7 +536,7 @@ func parseLedgerAccountType(reader params.Reader, key string) (ledgerv1.AccountT
case int64:
return ledgerv1.AccountType(v), nil
default:
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, fmt.Errorf("open_account: account_type is required")
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("open_account: account_type is required")
}
}
@@ -481,7 +551,7 @@ func parseLedgerAccountTypeString(value string) (ledgerv1.AccountType, error) {
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE, nil
default:
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, fmt.Errorf("open_account: invalid account_type")
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("open_account: invalid account_type")
}
}
@@ -518,15 +588,15 @@ func parseLedgerCharges(reader params.Reader) ([]*ledgerv1.PostingLine, error) {
for i, item := range items {
raw, ok := item.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("charges[%d]: invalid charge entry", i)
return nil, merrors.InvalidArgument(fmt.Sprintf("charges[%d]: invalid charge entry", i))
}
accountRef := strings.TrimSpace(fmt.Sprint(raw["ledger_account_ref"]))
if accountRef == "" {
return nil, fmt.Errorf("charges[%d]: ledger_account_ref is required", i)
return nil, merrors.InvalidArgument(fmt.Sprintf("charges[%d]: ledger_account_ref is required", i))
}
money, err := parseMoneyFromMap(raw)
if err != nil {
return nil, fmt.Errorf("charges[%d]: %w", i, err)
return nil, merrors.InvalidArgumentWrap(err, fmt.Sprintf("charges[%d]: invalid money", i))
}
lineType := parseLedgerLineType(fmt.Sprint(raw["line_type"]))
result = append(result, &ledgerv1.PostingLine{
@@ -553,12 +623,12 @@ func parseLedgerLineType(value string) ledgerv1.LineType {
func parseMoneyFromMap(raw map[string]interface{}) (*moneyv1.Money, error) {
if raw == nil {
return nil, fmt.Errorf("money is required")
return nil, merrors.InvalidArgument("money is required")
}
amount := strings.TrimSpace(fmt.Sprint(raw["amount"]))
currency := strings.TrimSpace(fmt.Sprint(raw["currency"]))
if amount == "" || currency == "" {
return nil, fmt.Errorf("money is required")
return nil, merrors.InvalidArgument("money is required")
}
return &moneyv1.Money{
Amount: amount,