service backend
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful

This commit is contained in:
Stephan D
2025-11-07 18:35:26 +01:00
parent 20e8f9acc4
commit 62a6631b9a
537 changed files with 48453 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
package model
import (
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/model"
)
// Account represents a ledger account that holds balances for a specific currency.
type Account struct {
storable.Base `bson:",inline" json:",inline"`
model.PermissionBound `bson:",inline" json:",inline"`
AccountCode string `bson:"accountCode" json:"accountCode"` // e.g., "asset:cash:usd"
Currency string `bson:"currency" json:"currency"` // ISO 4217 currency code
AccountType AccountType `bson:"accountType" json:"accountType"` // asset, liability, revenue, expense
Status AccountStatus `bson:"status" json:"status"` // active, frozen, closed
AllowNegative bool `bson:"allowNegative" json:"allowNegative"` // debit policy: allow negative balances
IsSettlement bool `bson:"isSettlement,omitempty" json:"isSettlement,omitempty"` // marks org-level default contra account
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
}
// Collection implements storable.Storable.
func (*Account) Collection() string {
return AccountsCollection
}

View File

@@ -0,0 +1,27 @@
package model
import (
"time"
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// AccountBalance represents the current balance of a ledger account.
// This is a materialized view updated atomically with journal entries.
type AccountBalance struct {
storable.Base `bson:",inline" json:",inline"`
model.PermissionBound `bson:",inline" json:",inline"`
AccountRef primitive.ObjectID `bson:"accountRef" json:"accountRef"` // unique per account+currency
Balance string `bson:"balance" json:"balance"` // stored as string for exact decimal
Currency string `bson:"currency" json:"currency"` // ISO 4217 currency code
Version int64 `bson:"version" json:"version"` // for optimistic locking
LastUpdated time.Time `bson:"lastUpdated" json:"lastUpdated"` // timestamp of last balance update
}
// Collection implements storable.Storable.
func (*AccountBalance) Collection() string {
return AccountBalancesCollection
}

View File

@@ -0,0 +1,26 @@
package model
import (
"time"
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/model"
)
// JournalEntry represents an atomic ledger transaction with multiple posting lines.
type JournalEntry struct {
storable.Base `bson:",inline" json:",inline"`
model.PermissionBound `bson:",inline" json:",inline"`
IdempotencyKey string `bson:"idempotencyKey" json:"idempotencyKey"` // unique key for deduplication
EventTime time.Time `bson:"eventTime" json:"eventTime"` // business event timestamp
EntryType EntryType `bson:"entryType" json:"entryType"` // credit, debit, transfer, fx, fee, adjust, reverse
Description string `bson:"description" json:"description"`
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
Version int64 `bson:"version" json:"version"` // for ordering and optimistic locking
}
// Collection implements storable.Storable.
func (*JournalEntry) Collection() string {
return JournalEntriesCollection
}

View File

@@ -0,0 +1,27 @@
package model
import (
"time"
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/model"
)
// OutboxEvent represents a pending event to be published to NATS.
// Part of the transactional outbox pattern for reliable event delivery.
type OutboxEvent struct {
storable.Base `bson:",inline" json:",inline"`
model.OrganizationBoundBase `bson:",inline" json:",inline"`
EventID string `bson:"eventId" json:"eventId"` // deterministic ID for NATS Msg-Id deduplication
Subject string `bson:"subject" json:"subject"` // NATS subject to publish to
Payload []byte `bson:"payload" json:"payload"` // JSON-encoded event data
Status OutboxStatus `bson:"status" json:"status"` // pending, sent, failed
Attempts int `bson:"attempts" json:"attempts"` // number of delivery attempts
SentAt *time.Time `bson:"sentAt,omitempty" json:"sentAt,omitempty"`
}
// Collection implements storable.Storable.
func (*OutboxEvent) Collection() string {
return OutboxCollection
}

View File

@@ -0,0 +1,24 @@
package model
import (
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// PostingLine represents a single debit or credit line in a journal entry.
type PostingLine struct {
storable.Base `bson:",inline" json:",inline"`
model.PermissionBound `bson:",inline" json:",inline"`
JournalEntryRef primitive.ObjectID `bson:"journalEntryRef" json:"journalEntryRef"`
AccountRef primitive.ObjectID `bson:"accountRef" json:"accountRef"`
Amount string `bson:"amount" json:"amount"` // stored as string for exact decimal, positive = credit, negative = debit
Currency string `bson:"currency" json:"currency"` // ISO 4217 currency code
LineType LineType `bson:"lineType" json:"lineType"` // main, fee, spread, reversal
}
// Collection implements storable.Storable.
func (*PostingLine) Collection() string {
return PostingLinesCollection
}

View File

@@ -0,0 +1,78 @@
package model
import "github.com/tech/sendico/pkg/model"
// Collection names used by the ledger persistence layer.
const (
AccountsCollection = "ledger_accounts"
JournalEntriesCollection = "journal_entries"
PostingLinesCollection = "posting_lines"
AccountBalancesCollection = "account_balances"
OutboxCollection = "outbox"
)
// AccountType defines the category of account (asset, liability, revenue, expense).
type AccountType string
const (
AccountTypeAsset AccountType = "asset"
AccountTypeLiability AccountType = "liability"
AccountTypeRevenue AccountType = "revenue"
AccountTypeExpense AccountType = "expense"
)
// AccountStatus tracks the operational state of an account.
type AccountStatus string
const (
AccountStatusActive AccountStatus = "active"
AccountStatusFrozen AccountStatus = "frozen"
AccountStatusClosed AccountStatus = "closed"
)
// EntryType categorizes journal entries by their business purpose.
type EntryType string
const (
EntryTypeCredit EntryType = "credit"
EntryTypeDebit EntryType = "debit"
EntryTypeTransfer EntryType = "transfer"
EntryTypeFX EntryType = "fx"
EntryTypeFee EntryType = "fee"
EntryTypeAdjust EntryType = "adjust"
EntryTypeReverse EntryType = "reverse"
)
// LineType distinguishes the role of a posting line within a journal entry.
type LineType string
const (
LineTypeMain LineType = "main"
LineTypeFee LineType = "fee"
LineTypeSpread LineType = "spread"
LineTypeReversal LineType = "reversal"
)
// OutboxStatus tracks the delivery state of an outbox event.
type OutboxStatus string
const (
OutboxStatusPending OutboxStatus = "pending"
OutboxStatusSent OutboxStatus = "sent"
OutboxStatusFailed OutboxStatus = "failed"
)
// Money represents an exact decimal amount with its currency.
type Money struct {
Currency string `bson:"currency" json:"currency"`
Amount string `bson:"amount" json:"amount"` // stored as string for exact decimal representation
}
// LedgerMeta carries organization-scoped metadata for ledger entities.
type LedgerMeta struct {
model.OrganizationBoundBase `bson:",inline" json:",inline"`
RequestRef string `bson:"requestRef,omitempty" json:"requestRef,omitempty"`
TraceRef string `bson:"traceRef,omitempty" json:"traceRef,omitempty"`
IdempotencyKey string `bson:"idempotencyKey,omitempty" json:"idempotencyKey,omitempty"`
}