package model import ( "strings" "github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/model/account_role" "github.com/tech/sendico/pkg/mservice" "go.mongodb.org/mongo-driver/v2/bson" ) // AccountType defines the category of account (asset, liability, revenue, expense). type LedgerAccountType string const ( LedgerAccountTypeAsset LedgerAccountType = "asset" LedgerAccountTypeLiability LedgerAccountType = "liability" LedgerAccountTypeRevenue LedgerAccountType = "revenue" LedgerAccountTypeExpense LedgerAccountType = "expense" ) // AccountStatus tracks the operational state of an account. type LedgerAccountStatus string const ( LedgerAccountStatusActive LedgerAccountStatus = "active" LedgerAccountStatusFrozen LedgerAccountStatus = "frozen" LedgerAccountStatusClosed LedgerAccountStatus = "closed" ) type LedgerAccountScope string const ( LedgerAccountScopeOrganization LedgerAccountScope = "organization" LedgerAccountScopeSystem LedgerAccountScope = "system" ) type SystemAccountPurpose string const ( SystemAccountPurposeExternalSource SystemAccountPurpose = "external_source" SystemAccountPurposeExternalSink SystemAccountPurpose = "external_sink" ) // Account represents a ledger account that holds balances for a specific currency. type LedgerAccount struct { storable.Base `bson:",inline" json:",inline"` Describable `bson:",inline" json:",inline"` // Scope defines whether the account belongs to an organization // or is a system-level ledger account used for internal accounting. Scope LedgerAccountScope `bson:"scope" json:"scope"` // SystemPurpose specifies the role of a system-scoped account // (e.g., external source or sink of funds). Must be set for system accounts // and must be nil for organization accounts. SystemPurpose *SystemAccountPurpose `bson:"systemPurpose,omitempty" json:"systemPurpose,omitempty"` // OrganizationRef links the account to an organization. // Must be set for organization accounts and nil for system accounts. OrganizationRef *bson.ObjectID `bson:"organizationRef,omitempty" json:"organizationRef,omitempty"` // Role defines the functional purpose of the account within an organization // (e.g., pending, operating, settlement, hold, etc.). // Must be set for organization accounts and omitted for system accounts. Role account_role.AccountRole `bson:"role,omitempty" json:"role,omitempty"` // AccountCode is a logical classification code of the account // (e.g., "asset:cash:usd") used for reporting and grouping. AccountCode string `bson:"accountCode" json:"accountCode"` // Currency is the ISO 4217 currency code the account operates in. Currency string `bson:"currency" json:"currency"` // AccountType defines the accounting category of the account // (asset, liability, revenue, expense). AccountType LedgerAccountType `bson:"accountType" json:"accountType"` // Status represents the operational state of the account. Status LedgerAccountStatus `bson:"status" json:"status"` // AllowNegative defines whether the account is allowed to have // a negative balance (used for system control accounts). AllowNegative bool `bson:"allowNegative" json:"allowNegative"` // OwnerRef optionally links the account to a specific owner entity // (e.g., user or sub-entity within the organization). OwnerRef *bson.ObjectID `bson:"ownerRef,omitempty" json:"ownerRef,omitempty"` // Metadata holds additional arbitrary key-value attributes. Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"` } // Collection implements storable.Storable. func (*LedgerAccount) Collection() string { return mservice.LedgerAccounts } // Validate enforces scope-specific invariants for ledger accounts. func (a *LedgerAccount) Validate() error { if a == nil { return merrors.InvalidArgument("ledger account is required") } switch a.Scope { case LedgerAccountScopeOrganization: if a.OrganizationRef == nil || a.OrganizationRef.IsZero() { return merrors.InvalidArgument("organization_ref is required for organization accounts") } if strings.TrimSpace(string(a.Role)) == "" { return merrors.InvalidArgument("role is required for organization accounts") } if a.SystemPurpose != nil { return merrors.InvalidArgument("system_purpose must be nil for organization accounts") } case LedgerAccountScopeSystem: if a.OrganizationRef != nil && !a.OrganizationRef.IsZero() { return merrors.InvalidArgument("organization_ref must be nil for system accounts") } if strings.TrimSpace(string(a.Role)) != "" { return merrors.InvalidArgument("role must be empty for system accounts") } if a.SystemPurpose == nil { return merrors.InvalidArgument("system_purpose is required for system accounts") } default: return merrors.InvalidArgument("scope is required") } return nil }