Skip to main content

Data model

Two tables carry the whole system: an immutable event store (the transaction history itself) and a derived GL journal (double-entry rows generated from events by configurable rules). Everything else — console, embed, API — is a read surface over these two.

Anatomy of an event

Every financial movement is one event row. Using a Meridian Travel Co. card payment as the example:

FieldSampleMeaning
eventTypeCC_PAYMENT_RECEIVEDWhat happened, as a business-readable code
trCodeCCRAccounting-compatible transaction code (see reference below)
trIndicatorAA = add, C = cancel/reversal
trSegmentPAYRECReporting segment: COST, PAYREC, REFUND, PAYMENT
amount2000.00Signed amount from the source record
currencyCodeUSDRow-level currency — multi-currency orgs are handled per event
itineraryName2001471The booking number your teams quote
itineraryTitleCanadian Rockies by RailThe trip label, for context
itineraryIda2w…Salesforce booking id (technical identity)
recordNamePM-100241Human label of the source record: payment auto-number, or item display name like Icefields Parkway Excursion
recordIda2v…Salesforce source record id (technical identity)
occurredAt2026-08-18T14:20:00ZWhen the event happened at the source
sourcesyncHow it was captured: real-time outbox, incremental sync, backfill
payload{ "paymentMethod": "Card" }Source context: raw payment method, item record type, wizard tab

Human references

Ids are the technical identity; the name fields are what humans work with. itineraryName holds only the booking number — when a booking has none, the field is null and surfaces fall back to the Salesforce id rather than showing a misleading value. The trip title is deliberately a separate field: it is shared by every booking on a departure and must never masquerade as a unique reference.

Events never change

The event store is append-only:

  • A cancelled option is a new OPTION_REMOVED event with a negative amount and a C indicator — the original OPTION_ADDED stays untouched.
  • A refund is a new CC_REFUND_ISSUED event, not an edit of the payment.
  • Unrecognized payment methods still produce an event (UNK / UNMAPPED_TRANSACTION) booked to a suspense account, so review lists are driven by the ledger itself.

The GL journal layer

Each event is run through your tenant's GL rules (txh/gl-rules.yaml in your tenant configuration) to produce balanced debit/credit journal entries — for example, a card payment debits Card Clearing and credits Accounts Receivable – Guests. The trial balance is a straight aggregation of these entries.

Because events are immutable and entries are derived, changing the chart of accounts is safe: editing the rules and replaying voids the existing entries and regenerates them from the same event stream. Nothing about the captured history is at risk in a GL restructure.

TR code reference

Classification is tenant configuration (txh/event-definitions.yaml): payment methods map by amount sign, items map by record type. The reference codes:

Payments and refunds

TR codeSegmentMeaning
CCR / RECPAYREC / REFUNDCard payment / card refund
CSH / REFPAYREC / REFUNDBank transfer, check, wire / cash-family refund
EGFPAYRECGift card applied or refunded
ATR / RTDCOSTTravel credit applied to / returned from a booking
MTRPAYRECPayment transferred between bookings
UNKUnmapped payment method (suspense)

Line items

TR codeMeaning
CSTTour cost: package, accommodation, activity
AIRAirfare
OPTOptional excursion
GPPTravel protection plan
PRH / PSHPre- / post-stay hotel
SPASpecial allowance / adjustment

A tenant's map can add methods and record types freely; the event types and double-entry treatment come with them. See Capturing events for how classification happens at the source.