Skip to content

Decide: does holt recognise revenue when the order is written, or when it is invoiced? #133

Description

@goetchstone

The question

computeDailyReconciliation compares two sides that are on different accounting bases:

  • source.revenue — every order written on the day. SALES_REVENUE_STATUSES includes ORDER, so an order placed today counts immediately.
  • journal.revenue — only orders carrying an invoice. generateSalesJournal defers everything else to Customer Deposits.

For a furniture retailer this diverges structurally. A special order written today and delivered in six weeks appears as revenue drift equal to its full value, every day until it is invoiced. The balanced flag goes false and the warnings list a "Revenue drift" that is not an error.

A daily control that reports a false discrepancy every day is one people stop reading — the same failure mode as the cash-bucket bug fixed alongside this one.

Why this is not just a bug fix

Both readings are defensible and they are a business policy, not an implementation detail:

  1. Recognise on invoice (accrual). Today's journal behaviour is correct; the reconciliation's source side is measuring bookings and should either match the journal's basis or report deferral as its own reconciling item rather than as drift.
  2. Recognise on order. Bookings are the real measure and generateSalesJournal's if (!order.hasInvoices) gate is an artifact of how Ordorite modelled orders, inherited rather than chosen.

Picking one silently would set an accounting policy for every deployment from inside a test fixture.

Current state — pinned, not papered over

__tests__/integration/tradingDay.integration.test.ts stage 14 asserts the drift equals exactly the un-invoiced bookings:

const DEFERRED_REVENUE = 5000;
const DEFERRED_COST = 2500;
expect(recon.drift.revenue).toBeCloseTo(DEFERRED_REVENUE, 2);
expect(recon.drift.cost).toBeCloseTo(DEFERRED_COST, 2);

So the number stays explained. If it ever stops equalling the deferred amount, something other than deferral is wrong and the test says so.

What resolving this looks like

Whichever way it goes, DailyReconciliationSource.revenue / .cost and the drift warnings need to say which basis they are on, and the two sides need to agree. If option 1: add a deferred component so drift means genuine drift. If option 2: drop the invoice gate in buildJournalLines.

Documented in docs/domains/accounting.md under "Open: bookings vs recognised revenue".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions