Skip to content

Ledgers

Verified against source (14)

  • accounting/Ledger.cs
  • accounting/LedgerItem.cs
  • Config/Ledger.config
  • Config/LedgerItem.config
  • Config/LedgerTemplate.config
  • Config/LedgerTemplateItem.config
  • Properties/Settings.settings
  • Tables/Ledger.sql
  • Interfaces/IPaymentProvider.cs
  • qbo4.Payment.AuthorizeNet/PaymentProvider.cs
  • Usage/LedgerUsageHandler.cs
  • Usage/LedgerUsageOptions.cs
  • Extensions/AbstractExtensions.cs
  • Extensions/ServiceCollectionExtensions.cs

Ledgers use four classes, each with the standard routes: /api/{module}/{operation} runs synchronously, and /api/{module}/queue/{operation} queues the operation for an app server.

Class Module Purpose
LedgerObject ledger One ledger, bound to a parent through Object / ObjectID. Balance and BalanceAdjusted are computed; don’t set them.
LedgerItemObject ledgeritem One line: AmountMoney, AmountDecimal, Units, UnitCount, Rate, IsDebit, IsTotaled, PostedDate, PaidDate, TransactionNumber, and an optional ChildLedgerID.
LedgerTemplateObject ledgertemplate Implements ITemplate; adds RefreshOnInsert, CalculateMethod, ProjectMethod, InvoiceTemplateID, InvoiceMatrixID.
LedgerTemplateItemObject ledgertemplateitem A template line: defaults, IsDebit, IsTotaled, CanExclude, CanRefresh, ManualInsert, MatrixID.
POST /api/ledger/save?Template=Case Fees&Object=Process&ObjectID=1001

On insert, a database trigger adds a LedgerItem for each template item whose ManualInsert is 0 or 2, copying its name, type, sequence, debit and totaled flags, lock, units, and default amount. If the template has RefreshOnInsert, the ledger is then calculated before the call returns. The template’s Repeatable setting applies as described in Templates.

GET /api/ledger/calculate?ID=55
GET /api/ledger/calculate?ID=55&RecalculateItem=false
  1. Project items (unless RecalculateItem=false). Without a template ProjectMethod, QBO runs the ProjectionListGoodThrough statement (with GoodThrough, defaulting to now) and calculates each row: a child ledger’s adjusted balance, then the item’s matrix measures (applied by property name), then template defaults.
  2. Total. Without a template CalculateMethod, the Calculate statement sets Balance to the sum of debits minus credits over items with an AmountMoney, and BalanceAdjusted to the same sum over items where IsTotaled is true. IsDebit and IsTotaled fall back to the template item when null on the line; a null IsDebit counts as a credit.
  3. Roll up to any LedgerItem whose ChildLedgerID is this ledger, setting its AmountMoney to this ledger’s BalanceAdjusted.

A custom CalculateMethod must return Balance and BalanceAdjusted in its first row.

Saving a LedgerItem re-totals its ledger without re-projecting. Inserting an item fails with an alert when the ledger’s IsExpandable is false; updating a locked item fails likewise.

To preview projected lines without saving them:

GET /api/ledgeritem/project?LedgerID=55&GoodThrough=2026-12-31
GET /api/ledger/approve?ID=55,56
GET /api/ledger/post?ID=57
Operation Effect
/api/ledger/approve If the template has an InvoiceMatrixID or InvoiceTemplateID and the ledger has no invoice yet, generates the invoice (the matrix’s AttachmentTemplate measure, looked up against the parent, wins). Sets Completed, Status = Approved, IsLocked = true, then runs LockItems.
/api/ledger/post Sets PostedDate, Status = Posted, IsLocked = true, then runs LockItems.
/api/ledger/lockitems Locks every item on the listed ledgers.

Both refuse, with an alert, when the ledger is locked (without the LedgerUpdateLocked permission) or already in the target status.

Operation Effect
/api/ledgeritem/calculate?ID=… Recalculates and saves the items.
/api/ledgeritem/includeintotal?ID=… Sets IsTotaled on unlocked items.
/api/ledgeritem/excludefromtotal?ID=… Clears IsTotaled on unlocked items whose template allows exclusion.
/api/ledgeritem/toggleistotaled?ID=… Flips IsTotaled and re-totals the ledger and its parent line.
/api/ledgeritem/approve?ID=…, /api/ledgeritem/reject?ID=… Sets status Approved (and totals the item) or Rejected (and excludes it); used for timesheet entries.
/api/ledgeritem/recalculate?ID=… Re-totals the ledgers that own the items.
GET /api/ledger/authorizeandcapture?ID=55

For each ledger, QBO takes BalanceAdjusted (or Balance). If it is greater than zero, QBO finds the parent record’s Active payment method and invokes that payment method’s AuthorizeAndCapture service with the LedgerID and PaymentMethodID; with no active payment method it throws Invalid PaymentMethod. If nothing is owed, the ledger is posted instead. A locked ledger throws unless the caller has LedgerUpdateLocked.

Gateways implement IPaymentProvider, which covers customers, addresses, stored (vaulted) payment methods, authorize, capture, void, refund, one-time payments, and subscriptions behind one interface. Card and bank details are vaulted with the gateway; QBO keeps the gateway’s identifiers. Authorize.Net is the shipped implementation. See Integrations.

POST /api/ledger/applypayment?LedgerID=55&AmountMoney=750.00&TransactionNumber=TX-1042

ApplyPayment requires AmountMoney. It:

  1. Creates a payment line on the ledger, labeled and typed Payment by default (settings ApplyPaymentDefaultLabel and ApplyPaymentType), posted now. Other parameters, such as TransactionNumber or Description, are set on that line.
  2. Adds any funds left unapplied by earlier payments.
  3. Walks the ledger’s unpaid items (no PaidDate, AmountMoney ≥ 0). Each item the remaining funds cover in full gets a Payment Applied line in the payment’s child ledger, referencing the item. It stops at the first item it can’t cover; partial payments are off.
  4. Records any remainder as an Unapplied Funds line (setting ApplyPaymentUnappliedFunds) and in the payment’s AmountMoneyCountered.

It returns the saved payment LedgerItem, and throws if the ledger has no items to pay.

Register AddLedgerUsageTracking (configuration section GenerativeAI:LedgerUsage) to write AI usage to ledgers. Then pass Chat_LedgerID to an AI operation that accepts chat options (aiimport, aireview, aiupdate, aiupdatexml):

GET /api/attachment/aireview?ID=7&Template={message template}&Agent={agent}&Chat_LedgerID=42

For each call, QBO adds up to four LedgerItem rows, one per token type with a non-zero count: Input, CachedInput, Output, and Reasoning. Each row has Units set to the token type, UnitCount and AmountDecimal set to the token count, LedgerItemType set to the model, IsDebit and IsTotaled true, and a shared TransactionNumber (the conversation ID, or a new GUID) grouping the call.

Option Purpose
InputRatesPerMToken, OutputRatesPerMToken Dollars per million tokens, keyed by model ID (exact or prefix match).
CachedInputRatesPerMToken, ReasoningRatesPerMToken Optional; fall back to the input and output rates.
DefaultInputRatePerMToken, DefaultOutputRatePerMToken, … Rates for models not listed. With no rate, AmountMoney is left empty and only tokens are recorded.
LedgerTemplateItemID, LedgerTemplateItemIDs Template line for the rows: per token type, then per call (Chat_LedgerTemplateItemID), then the default.

Rows are written as the user who made the AI call; calls without a trusted user, or without a ledger, are skipped.