ledger

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package ledger provides double-entry accounting primitives for Maniflex applications.

Quick start

server.MustRegister(ledger.Models()...)
l := ledger.New(server)

// Inside a Service middleware:
entry, err := l.Post(ctx,
    ledger.EntryInput{Description: "Sale invoice #1001", PeriodID: "2026-05", Date: time.Now()},
    []ledger.LineInput{
        {AccountID: "ar-001", Debit: money.New(100_00, "SAR")},
        {AccountID: "rev-001", Credit: money.New(100_00, "SAR")},
    },
)

Models

The package provides three Maniflex model types that become standard REST endpoints when registered:

  • LedgerAccount — chart of accounts
  • LedgerEntry — journal entry header (read-only once posted)
  • LedgerLine — individual debit or credit leg

Balance queries

bal, err := ledger.Balance(ctx, "ar-001", "SAR", time.Now())
tb, err  := ledger.TrialBalance(ctx, "SAR", time.Now())

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrImbalanced is returned when the sum of debit cents does not equal the
	// sum of credit cents for any currency represented in the entry.
	ErrImbalanced = errors.New("ledger: entry is not balanced")

	// ErrNoLines is returned when Post is called with fewer than two lines.
	ErrNoLines = errors.New("ledger: entry must have at least two lines")
)

Sentinel errors returned by Post.

Functions

func Balance

func Balance(ctx *maniflex.ServerContext, accountID, currency string, asOf time.Time) (money.Amount, error)

Balance returns the net balance of accountID in the given currency as of asOf. Positive = net debit balance; negative = net credit balance.

ctx.Tx is honoured: when an active transaction is set, the query runs within it.

func Models

func Models() []any

Models returns the three model types to pass to server.MustRegister.

server.MustRegister(ledger.Models()...)
l := ledger.New(server)

Types

type AccountBalance

type AccountBalance struct {
	AccountID string
	Currency  string
	// Balance is positive for a net debit balance, negative for a net credit
	// balance. Whether a positive or negative balance is "normal" depends on
	// the account type (asset/expense accounts have normal debit balances;
	// liability/equity/revenue have normal credit balances).
	Balance money.Amount
}

AccountBalance is one row in a TrialBalance result.

func TrialBalance

func TrialBalance(ctx *maniflex.ServerContext, currency string, asOf time.Time) ([]AccountBalance, error)

TrialBalance returns the net balance of every account that has any activity in the given currency as of asOf, ordered by account_id.

type EntryInput

type EntryInput struct {
	Description string
	Reference   string    // optional memo / source document reference
	PeriodID    string    // e.g. "2026-05" or "2026-Q2" — opaque to pkg/ledger
	Date        time.Time // accounting date (may differ from PostedAt)
}

EntryInput carries the journal entry header for a Post call.

type Ledger

type Ledger struct{}

Ledger provides the Post operation.

func New

func New() *Ledger

New returns a Ledger. The models from Models() must be registered on the server before calling Post.

func (*Ledger) Post

func (l *Ledger) Post(ctx *maniflex.ServerContext, entry EntryInput, lines []LineInput) (*LedgerEntry, error)

Post records a balanced journal entry atomically.

Validation (performed before any DB work):

  • At least two lines are required.
  • Each line must have exactly one non-zero amount (Debit or Credit).
  • Amounts must be non-negative.
  • Each line must specify an AccountID and a currency (via the non-zero Amount).
  • Sum of debits must equal sum of credits per currency.

If ctx.Tx is already set, Post joins the active transaction and does not commit — the caller is responsible. Otherwise Post opens and commits its own transaction; if any step fails the transaction is rolled back.

PostedAt on the returned entry is set to time.Now().UTC().

type LedgerAccount

type LedgerAccount struct {
	maniflex.BaseModel
	Code     string `json:"code"      db:"code"      mfx:"required,unique,filterable,sortable"`
	Name     string `json:"name"      db:"name"      mfx:"required,filterable,sortable,searchable"`
	Type     string `json:"type"      db:"type"      mfx:"required,filterable,enum:asset|liability|equity|revenue|expense"`
	ParentID string `json:"parent_id" db:"parent_id" mfx:"filterable"`
}

LedgerAccount is a chart-of-accounts entry. Register it via Models().

type LedgerEntry

type LedgerEntry struct {
	maniflex.BaseModel
	Description string     `json:"description" db:"description" mfx:"required,filterable,searchable"`
	Reference   string     `json:"reference"   db:"reference"   mfx:"filterable"`
	PeriodID    string     `json:"period_id"   db:"period_id"   mfx:"required,filterable"`
	Date        time.Time  `json:"date"        db:"date"        mfx:"required,filterable,sortable"`
	PostedAt    *time.Time `json:"posted_at"   db:"posted_at"   mfx:"readonly,filterable,sortable"`
}

LedgerEntry is a journal entry header. PostedAt is set automatically by Post() and is read-only via the REST API.

type LedgerLine

type LedgerLine struct {
	maniflex.BaseModel
	EntryID     string `json:"entry_id"     db:"entry_id"     mfx:"required,filterable"`
	AccountID   string `json:"account_id"   db:"account_id"   mfx:"required,filterable"`
	DebitCents  int64  `json:"debit_cents"  db:"debit_cents"  mfx:"filterable,sortable"`
	CreditCents int64  `json:"credit_cents" db:"credit_cents" mfx:"filterable,sortable"`
	Currency    string `json:"currency"     db:"currency"     mfx:"required,filterable"`
	Notes       string `json:"notes"        db:"notes"        mfx:"filterable"`
}

LedgerLine is one leg of a journal entry. Exactly one of DebitCents or CreditCents is non-zero.

type LineInput

type LineInput struct {
	AccountID string
	Debit     money.Amount // set for debit legs; leave zero for credit legs
	Credit    money.Amount // set for credit legs; leave zero for debit legs
	Notes     string       // optional free-text annotation
}

LineInput is one debit or credit leg. Set Debit or Credit — not both. Currency is taken from whichever Amount is non-zero.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL