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:
| Field | Sample | Meaning |
|---|---|---|
eventType | CC_PAYMENT_RECEIVED | What happened, as a business-readable code |
trCode | CCR | Accounting-compatible transaction code (see reference below) |
trIndicator | A | A = add, C = cancel/reversal |
trSegment | PAYREC | Reporting segment: COST, PAYREC, REFUND, PAYMENT |
amount | 2000.00 | Signed amount from the source record |
currencyCode | USD | Row-level currency — multi-currency orgs are handled per event |
itineraryName | 2001471 | The booking number your teams quote |
itineraryTitle | Canadian Rockies by Rail | The trip label, for context |
itineraryId | a2w… | Salesforce booking id (technical identity) |
recordName | PM-100241 | Human label of the source record: payment auto-number, or item display name like Icefields Parkway Excursion |
recordId | a2v… | Salesforce source record id (technical identity) |
occurredAt | 2026-08-18T14:20:00Z | When the event happened at the source |
source | sync | How 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_REMOVEDevent with a negative amount and aCindicator — the originalOPTION_ADDEDstays untouched. - A refund is a new
CC_REFUND_ISSUEDevent, 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 code | Segment | Meaning |
|---|---|---|
CCR / REC | PAYREC / REFUND | Card payment / card refund |
CSH / REF | PAYREC / REFUND | Bank transfer, check, wire / cash-family refund |
EGF | PAYREC | Gift card applied or refunded |
ATR / RTD | COST | Travel credit applied to / returned from a booking |
MTR | PAYREC | Payment transferred between bookings |
UNK | — | Unmapped payment method (suspense) |
Line items
| TR code | Meaning |
|---|---|
CST | Tour cost: package, accommodation, activity |
AIR | Airfare |
OPT | Optional excursion |
GPP | Travel protection plan |
PRH / PSH | Pre- / post-stay hotel |
SPA | Special 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.