Busflow Docs

Internal documentation portal

Skip to content

Commerce Database Schema

This document details the concrete physical database schema for the Commerce & Finance Bounded Context (schema: commerce). The Commerce schema acts as the conversion and accounting engine, focusing on B2C/B2B sales, capacity holds, cross-border manifest data, and generalized taxation logic based on actuals.

Physical Entity Relationship Diagram

mermaid
erDiagram
    TOUR_OFFERING ||--o{ TOUR_OFFERING_PRICE : "priced by"
    TOUR_OFFERING ||--o{ CHECKOUT_SESSION : "initiates"
    TOUR_OFFERING ||--o{ BOOKING : "receives"

    CHECKOUT_SESSION |o--o| BOOKING : "converts to"

    BOOKING ||--|{ PASSENGER : "includes"
    BOOKING ||--|{ PAYMENT : "processes"
    BOOKING ||--o{ ANCILLARY : "adds"
    BOOKING ||--o{ INVOICE : "billed by"
    TOUR_OFFERING ||--|| FINANCIAL_LEDGER : "creates"

    PASSENGER ||--o{ TICKET : "issued to"
    SEAT_RESERVATION |o--o| PASSENGER : "reserves for"

    FINANCIAL_LEDGER ||--o{ TAX_LEDGER_ENTRY : "calculates tax"
    FINANCIAL_LEDGER ||--o{ INVOICE : "generates"

    INVOICE ||--o{ INVOICE_CANCELLATION : "is cancelled by"
    RECONCILIATION_UPLOAD ||--o{ RECONCILIATION_ENTRY : "contains"

Table Definitions

Note on tenant_id: In the Commerce schema, tenant_id is a soft reference (UUID) to the operators table in the Backoffice schema. Hard foreign keys cannot cross database schemas.

CAUTION

Global Conventions:

  1. All mutable tables include an updated_at TIMESTAMPTZ DEFAULT now() column.
  2. All tables include an index on (tenant_id). Other common indexes include (tenant_id, tour_offering_id) on bookings, and (booking_id) on child entities like passengers and payments.

tour_offerings

The sellable projection of a Backoffice TourDeparture. The system creates/updates this entity when Backoffice publishes a departure via the TripPublished event. The tour_offering_prices table supplies pricing — a read-model synced from the Backoffice PriceMatrix via PriceMatrixPublished events.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique offering identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
tour_departure_idUUIDNot NullSoft FK to backoffice.tour_departures (source departure)
tour_template_idUUIDNot NullSoft FK to backoffice.tour_templates
costing_sheet_idUUIDNot NullSoft FK to backoffice.costing_sheets
charter_quote_idUUIDSoft FK to backoffice.charter_quotes (B2B lineage)
active_price_matrix_idUUIDSoft FK to backoffice.price_matrices — the currently published PriceMatrix version. Updated via PriceMatrixPublished events.
titleVARCHARNot NullDenormalized display title
descriptionTEXTDenormalized display description
start_dateTIMESTAMPNot NullStart of the offering
end_dateTIMESTAMPNot NullEnd of the offering
statusVARCHARNot NullE.g., SCHEDULED, CANCELLED, COMPLETED
available_ancillariesJSONBNullableDenormalized array of resolved ancillary catalog items for this offering. Populated from TripPublished payload. See ancillary-catalog.md §Commerce Projection.
available_boarding_pointsJSONBNullableDenormalized array of resolved boarding point library items for this offering. Populated from TripPublished payload. Each entry includes stop name, address, coordinates, surcharge, door pickup availability + surcharge + radius, and is_origin flag. The booking widget reads this directly — no cross-schema query to Backoffice. See boarding-points.md §Commerce Projection.
max_door_pickupsINTNullableMaximum door pickup bookings for this offering. Denormalized from TripPublished payload (source: tour_templates.max_door_pickups). Commerce enforces this at checkout time.

checkout_sessions

A short-lived entity tracking purchase intent before confirmation. Lifecycle: ACTIVEEXPIRED (TTL-based cleanup) or CONVERTED (successful payment). Default TTL: 30 minutes (expires_at = created_at + 30 min).

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique session identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
tour_offering_idUUIDForeign Key (tour_offerings.id), Not NullOffering under checkout
booking_idUUIDForeign Key (bookings.id)Converted booking ID. Set when session converts to a Booking.
session_typeVARCHARNot Null, Default: BOOKINGBOOKING (standard B2C/B2B checkout) or ONBOARD_SALE (driver-initiated payment link). Determines event routing on expiry — see sweep note below.
session_tokenVARCHARUnique, Not NullFingerprint / token
statusVARCHARNot Null, Default: ACTIVEACTIVE (in progress), EXPIRED (TTL elapsed, no payment), CONVERTED (successfully became a Booking)
expires_atTIMESTAMPNot NullTTL expiration time. Default: created_at + interval '30 minutes' for BOOKING; operator-configured for ONBOARD_SALE (see operator_settings.onboard_payment_link_ttl_minutes).
selected_optionsJSONBTyped structure: { passenger_count: int, boarding_point_id: uuid, is_door_pickup: boolean, door_pickup_address?: { formatted_address: string, lat: number, lng: number }, ancillary_ids: uuid[], seat_selections: [{ service_leg_id, seat_identifier }], demographic_breakdown: [{ demographic: string, count: int }] }. boarding_point_id always references backoffice.boarding_point_library. is_door_pickup indicates the pickup mode (stop vs. door). When is_door_pickup = true, door_pickup_address carries the geocoded passenger address validated against the stop's radius.
created_atTIMESTAMPDefault: now()Session creation timestamp

NOTE

Abandoned Cart / Payment Link Sweep: A Hasura Scheduled Trigger (checkout_abandoned_sweep) runs every 5 minutes. It sets status = 'EXPIRED' for all rows where status = 'ACTIVE' AND expires_at < now(). Event routing by session_type:

session_typeEvent EmittedConsumerSide Effects
BOOKINGCheckoutAbandonedCommunicationsRe-engagement workflow: Email at expired_at + 1h, WhatsApp at expired_at + 24h (if no response). Opt-out via Contact.marketing_consent.
ONBOARD_SALEOnboardPaymentExpiredOperationsSets onboard_sales.payment_status → FAILED, notifies driver via push. No re-engagement — driver decides next step.

bookings

The primary transactional container.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique booking identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
tour_offering_idUUIDForeign Key (tour_offerings.id), Not NullBooked offering
reseller_idUUIDSoft FK to backoffice.resellers
reference_numberVARCHARUnique, Not NullLook-up reference for bank imports
source_channelVARCHARNot NullEnum of booking origin
statusVARCHARNot NullEnum: DRAFT, PENDING_PAYMENT, DEPOSIT_PAID, FULLY_PAID, COMPLETED, CANCELLED, REFUNDED, NO_SHOW. See booking-lifecycle-protocol.md for the full state machine, Hasura Actions, and Scheduled Triggers.
total_amountDECIMALNot NullTotal transaction amount
currencyVARCHARDefault: EUR
created_atTIMESTAMPDefault: now()

passengers

The individual traveler on a booking.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique passenger identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
booking_idUUIDForeign Key (bookings.id), Not NullThe parent booking
passenger_profile_idUUIDSoft FK to backoffice.passenger_profiles
boarding_point_idUUIDSoft FK to backoffice.boarding_point_library. Always references a single table — no polymorphic FK.
is_door_pickupBOOLEANNot Null, Default: falseIndicates pickup mode: false = boards at the physical stop, true = door pickup at their address.
door_pickup_addressJSONBNullable{ formatted_address: string, lat: number, lng: number }. Set when is_door_pickup = true. The geocoded passenger address validated against the stop's door_pickup_radius_km.
is_primary_contactBOOLEANDefault: falseDistinguishes billing contact
first_nameVARCHARNot Null
last_nameVARCHARNot Null
emailVARCHARHighly recommended for primary contact
phoneVARCHAR
date_of_birthDATERequired for border manifests
document_numberVARCHARPassport/ID for manifests
nationalityVARCHAR
statusVARCHARNot Null, Default: ACTIVEACTIVE or CANCELLED. Enables partial cancellation — remove one passenger without cancelling the entire booking.

payments

Granular tracking for transactions via Mollie Marketplaces. Supports split payments (deposit + final payment) and refunds. The backoffice.operator_integrations table (type = MOLLIE) stores Mollie sub-account configuration — see schema-backoffice.md §operator_integrations. For the full Mollie integration spec (webhook mapping, sub-account provisioning, payout scheduling), see PRODUCT_mollie-integration.md.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique payment identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
booking_idUUIDForeign Key (bookings.id), Not NullParent booking
providerVARCHARNot NullMOLLIE (Standard platform processing)
provider_transaction_idVARCHARUniqueMollie transaction ID
payment_typeVARCHARNot NullDEPOSIT, FINAL_PAYMENT, REFUND (full), or PARTIAL_REFUND (single passenger/ancillary)
refund_passenger_idUUIDForeign Key (passengers.id)Nullable. Links a PARTIAL_REFUND to the specific passenger removed.
refund_ancillary_idUUIDForeign Key (ancillaries.id)Nullable. Links a PARTIAL_REFUND to the specific ancillary removed.
payment_methodVARCHARActual method used: SEPA, PAYPAL, APPLE_PAY, GOOGLE_PAY, KLARNA, CREDIT_CARD, IDEAL. Populated from Mollie webhook method field.
amountDECIMALNot NullTransaction amount
currencyVARCHARNot NullISO 4217
statusVARCHARNot NullPENDING, COMPLETED, FAILED, REFUNDED
processed_atTIMESTAMPTimestamp of final status from Mollie webhook

ancillaries

Upsells added to a booking. Each ancillary has an independent lifecycle to support partial cancellation/refund of individual upsells.

The type column defines the behavioral category (determines system behavior like refund rules and reporting). The label column is operator-defined (the display name passengers see, e.g., "Reiserücktrittsversicherung" or "Premium Sitzplatz"). Operators configure their available ancillaries per TourTemplate via ancillary-catalog.md §Template-Level Assignment.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique ancillary identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
booking_idUUIDForeign Key (bookings.id), Not NullParent booking
typeVARCHARNot NullBehavioral category: INSURANCE, UPGRADE (room/seat), EXTRA_LUGGAGE, EXCURSION, MEAL, BOARDING_SURCHARGE, OTHER. Determines system behavior (refund eligibility, reporting bucket). BOARDING_SURCHARGE is auto-created by Commerce when a passenger selects a boarding point with a surcharge — see boarding-points.md §Door Pickup Mechanics.
labelVARCHARNot NullOperator-defined display name shown to passengers (e.g., "Einzelzimmerzuschlag", "Ganztagesausflug Venedig")
quantityINTNot Null, Default: 1Number of units (e.g., 2 extra luggage allowances)
priceDECIMALNot NullUnit price of the upsell
currencyVARCHARNot NullISO 4217
statusVARCHARNot Null, Default: ACTIVEACTIVE, CANCELLED, REFUNDED. Independent of parent Booking status.
catalog_item_idUUIDNullableSoft FK to backoffice.ancillary_catalog_items. Set when the ancillary originates from the operator's catalog. Null for system-generated ancillaries (e.g., BOARDING_SURCHARGE) or manually added items. Enables Backoffice → Commerce traceability.

seat_reservations

Time-locked capacity holds for operations.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique reservation identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
service_leg_idUUIDNot NullSoft FK to operations.service_legs
passenger_idUUIDForeign Key (passengers.id)Assigned passenger
seat_identifierVARCHARNot NullKey mapping to layout
statusVARCHARNot NullHELD, CONFIRMED, RELEASED
hold_expires_atTIMESTAMPExpiration for HELD seats

IMPORTANT

Partial unique index: UNIQUE(service_leg_id, seat_identifier) WHERE status IN ('HELD', 'CONFIRMED'). Prevents double-booking the same physical seat on the same leg. The partial unique index excludes RELEASED reservations — the system can release a seat and re-hold it for a different passenger.

NOTE

Seat Hold Cleanup: A Hasura Scheduled Trigger (seat_hold_cleanup) runs every 60 seconds. It sets status = 'RELEASED' for all rows where status = 'HELD' AND hold_expires_at < now(). This prevents ghost holds from blocking inventory. The hold_expires_at TTL is set to match the checkout_sessions.expires_at value (default: 30 minutes) to prevent users from losing their seat hold while still in an active checkout session — see ADR-013.

NOTE

Vehicle Swap Remapping: During vehicle swaps, the swap handler remaps seat_reservations to the new vehicle's seat map via a 3-phase algorithm (match-by-ID → match-by-type → downgrade). The algorithm sets reservations that cannot match to RELEASED. See vehicle-swap-protocol.md for the algorithm, priority rules, and edge states.

tickets

Digital fulfillment and boarding pass.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique ticket identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
passenger_idUUIDForeign Key (passengers.id), Not NullTicket owner
ticket_numberVARCHARUnique, Not NullHuman-readable identifier
qr_hashVARCHARUnique, Not NullValidation hash
statusVARCHARNot Null, Default: ACTIVEACTIVE or VOIDED. Voided tickets fail QR validation at boarding. Voided on booking cancellation or passenger removal.
issued_atTIMESTAMPDefault: now()

financial_ledgers

The Nachkalkulation (actuals vs planned margins). One row per TourOffering. Auto-created when the first booking reaches DEPOSIT_PAID. The system aggregates revenue from completed Payment records; the system aggregates expenses from ExpenseSubmitted and OnboardSaleRecorded domain events consumed from Operations via Hasura Event Triggers.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique ledger identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
tour_offering_idUUIDForeign Key (tour_offerings.id), Unique, Not NullParent departure/offering (one ledger per offering)
costing_sheet_idUUIDNot NullSoft FK to backoffice.costing_sheets — the Soll/Ist comparison baseline
planned_price_matrix_version_idUUIDNot NullSoft FK to backoffice.price_matrices — the PriceMatrix version used to compute planned_revenue. Immutable after creation.
statusVARCHARNot Null, Default: OPENOPEN (accepting event-driven mutations) or CLOSED (period-locked, finalized)
realized_revenueDECIMALDefault: 0Actual revenue = Σ Payment.amount WHERE status = COMPLETED across all bookings for this offering
realized_expenseDECIMALDefault: 0Actual expenses = Σ from ExpenseSubmitted + OnboardSaleRecorded events (Operations context)
planned_costDECIMALNot NullImmutable snapshot of CostingSheet.total_net_cost at ledger creation — provides the Soll baseline for cost comparison
planned_revenueDECIMALNot NullImmutable snapshot at ledger creation. V1: ADULT variant list_price × CapacityRule.max_capacity — upper-bound Soll baseline assuming 100% occupancy at highest-priced demographic. Version tracked via planned_price_matrix_version_id. V2 will expand to per-variant computation: Σ(PriceMatrix.variant.gross_price × expected pax per variant).
cost_deltaDECIMALrealized_expense − planned_cost. Positive = overspent vs. plan, negative = underspent.
revenue_deltaDECIMALrealized_revenue − planned_revenue. Positive = oversold/higher yield, negative = undersold vs. plan.
margin_deltaDECIMAL(realized_revenue − realized_expense) − (planned_revenue − planned_cost). Positive = outperforming plan, negative = underperforming.
currencyVARCHARNot Null, Default: EURISO 4217 currency code
created_atTIMESTAMPTZDefault: now()
closed_atTIMESTAMPTZTimestamp of finalization (set when status transitions to CLOSED)

tax_ledger_entries

Calculated taxation burdens. The system structures fields to satisfy the mandatory recording requirements of § 25 Abs. 5 UStG. Relationship: 1:N — a single FinancialLedger can have multiple entries when a departure mixes tax strategies (e.g., MARGIN_SCHEME_25 for tour margin + STANDARD_VAT for onboard sales).

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique tax entry identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
financial_ledger_idUUIDForeign Key (financial_ledgers.id), Not NullParent ledger
tax_strategyVARCHARNot NullSTANDARD_VAT or MARGIN_SCHEME_25
customer_gross_amountDECIMALNot Null§ 25 Abs. 5 Nr. 1: Der Betrag, den der Leistungsempfänger aufwendet
procurement_gross_amountDECIMALNot Null§ 25 Abs. 5 Nr. 2: Die Aufwendungen für Reisevorleistungen (brutto, ohne VSt-Abzug)
margin_taxable_netDECIMALNot Null§ 25 Abs. 5 Nr. 3/4: Steuerpflichtige Marge (Netto, EU). Bei Negativmarge = 0
margin_exempt_netDECIMALNot Null§ 25 Abs. 5 Nr. 4: Steuerfreie Marge (Drittlands-Vorleistungen, § 25 Abs. 2)
tax_base_amountDECIMALNot NullBemessungsgrundlage = margin_taxable_net
tax_amountDECIMALNot NullComputed tax = margin_taxable_net × tax_rate
tax_rateDECIMALNot NullApplicable rate (0.19 EU, 0.0 Drittland)
created_atTIMESTAMPTZDefault: now()Immutable creation timestamp for audit

IMPORTANT

§ 25 Abs. 5 UStG Aufzeichnungspflicht: Aus den Aufzeichnungen müssen vier exakte Werte hervorgehen: (1) Kundenaufwand, (2) Vorleistungskosten, (3) Bemessungsgrundlage, (4) Aufteilung steuerpflichtig/steuerfrei. Diese dürfen nicht nur on-the-fly berechnet werden, sondern müssen als unveränderliche Spalten persistiert werden.

invoices

Consolidated billing information and e-invoicing artifacts. The system generates invoices as PDFs directly from aggregated TaxLedgerEntry and Booking data without a persistent invoice_line_items table. At generation time, the system snapshots volatile data (supplier, recipient, line items) into JSONB columns to ensure GoBD-compliant immutability — even if source entities change later, the invoice remains unchanged. See invoice-service-protocol.md for the full § 14 UStG field mapping, InvoiceService contract, and credit note mechanism.

IMPORTANT

Invoice Numbering Strategy (GoBD): Tenant-scoped gap-free sequential numbering via a tenant_invoice_sequences helper table with SELECT ... FOR UPDATE row-level lock pattern. Format: {tenant_prefix}-{year}-{sequence} (e.g., BUS-2026-00042). The sequence resets per fiscal year per tenant configuration. This ensures GoBD-compliant uninterrupted numbering under concurrent inserts.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique invoice identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
financial_ledger_idUUIDForeign Key (financial_ledgers.id)Parent ledger
booking_idUUIDForeign Key (bookings.id), Not NullParent booking (1:1 — one non-cancelled invoice per booking)
invoice_numberVARCHARUnique, Not NullHuman-readable ID (Gap-free sequence)
issue_dateDATENot Null
due_dateDATENot Null
cancelledBOOLEANDefault: falseIndicator for Storno workflow
statusVARCHARNot NullDRAFT, ISSUED, PAID, VOIDED
supplier_snapshotJSONBNot NullOperator info at generation time: { company_name, address, tax_number, vat_id } from backoffice.operators
recipient_snapshotJSONBNot NullPrimary passenger info at generation time: { first_name, last_name, address }
line_items_snapshotJSONBNot NullRendered line items array. Structure: [{ position, description, quantity, unit_price, net_amount, tax_rate, tax_amount, gross_amount, tax_strategy }]
total_netDECIMALNot NullSum of net amounts across all line items
total_taxDECIMALNot NullSum of tax amounts
total_grossDECIMALNot NullGrand total (total_net + total_tax)

tenant_invoice_sequences

Helper table for GoBD-compliant gap-free sequential invoice numbering. Each row represents one tenant's counter for one fiscal year. The SELECT ... FOR UPDATE pattern ensures that concurrent invoice generation serializes at the DB level, preventing gaps.

ColumnTypeConstraintsDescription
tenant_idUUIDComposite PKSoft FK to backoffice.operators
fiscal_yearINTComposite PKFiscal year for this sequence
last_numberINTNot Null, Default: 0Last assigned sequence number

ledger_period_locks

Immutable lock mechanism ensuring no one can alter financial ledgers and invoices after a period closes or exports to DATEV.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique lock identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
period_startDATENot NullStart of locked period
period_endDATENot NullEnd of locked period
lock_typeVARCHARNot NullEXPORT (auto-locked via DATEV export) or MANUAL
locked_atTIMESTAMPDefault: now()
locked_byUUIDSoft FK to backoffice.users

invoice_cancellations

Tracks the GoBD-compliant cancellation (Storno) of a finalized invoice.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique cancellation identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
cancelled_invoice_idUUIDForeign Key (invoices.id)The voided invoice
replacement_invoice_idUUIDForeign Key (invoices.id)Optional linkage to the corrected new invoice
reasonTEXTNot NullBusiness reason for cancellation
cancelled_atTIMESTAMPDefault: now()

reconciliation_uploads

Records imports of external general ledger data (BWA/SuSa from DATEV) for Data Drift management.

WARNING

Subscription Tier Gating: reconciliation_uploads are an advanced B2B accounting feature. Insert operations require a PRO or ENTERPRISE plan (X-Hasura-Plan). See L3-6.2.2 Schema Matrix Enforcement for details.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique upload identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
periodVARCHARNot NullTarget financial period (e.g., 2026-04)
uploaded_file_urlVARCHARStorage reference
parsed_dataJSONBRaw parsed rows
statusVARCHARNot NullPENDING, PROCESSED, FAILED
uploaded_atTIMESTAMPDefault: now()

reconciliation_entries

Line-item comparisons surfacing Soll/Ist deviations between Commerce actuals and DATEV bookings.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique entry identifier
tenant_idUUIDNot NullSoft FK to backoffice.operators
reconciliation_upload_idUUIDForeign Key (reconciliation_uploads.id)Parent upload
account_codeVARCHARNot Nulle.g. SKR03/04 code
busflow_amountDECIMALAccumulated value in Busflow
datev_amountDECIMALValue reported from DATEV
deltaDECIMALAbsolute variance

change_events

Local audit trail for entities within the Commerce schema. Tracks GoBD-relevant mutations on invoices and ledgers. Uses a polymorphic entity reference (entity_type + entity_id) — see ADR-019. The shared AuditTrailService (NestJS) writes all entries.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique event
tenant_idUUIDNot NullSoft FK to backoffice.operators
user_idUUIDNullableSoft FK to auth.users(id). The actor — null for system/automation.
entity_typeVARCHARNot NullTarget entity type: tour_offering, booking, payment, ticket, invoice, financial_ledger, tax_ledger_entry, ledger_period_lock
entity_idUUIDNot NullID of the mutated entity. Soft FK — entity table varies by entity_type.
actionVARCHARNot NullINSERT, UPDATE, DELETE
scopeVARCHARNot Null, Default: GENERALGOBD, COMPLIANCE, DSGVO, CONFIG, GENERAL — canonical enum per ADR-019.
correlation_idUUIDNullableGroups related change_events across bounded contexts (e.g., vehicle swap writes to Operations + Commerce).
old_valuesJSONBFull row snapshot before mutation. Captured via SELECT ... FOR UPDATE in the AuditTrailService. Null for CREATE.
new_valuesJSONBState after mutation. Null for DELETE.
created_atTIMESTAMPTZDefault: now()

NOTE

Index: (tenant_id, entity_type, entity_id) — covers all audit trail queries. Additional partial index: (correlation_id) WHERE correlation_id IS NOT NULL.

NOTE

Scope mapping for this schema: GOBD for invoice, financial_ledger, tax_ledger_entry, payment, ledger_period_lock. GENERAL for everything else.


Read Models & Views

The following read-model tables exist within the commerce schema to enable cross-context reads without hard foreign keys, per the CQRS read model pattern.

NOTE

Boarding point data is denormalized as available_boarding_points JSONB on tour_offerings (see §tour_offerings). No separate SQL view or read-model table exists for boarding points — the booking widget reads directly from the tour_offerings row. This replaces the earlier pickup_options_view design.

tour_offering_prices

A read-model table (not a SQL view) synced from the Backoffice PriceMatrix via PriceMatrixPublished domain events. Stores flattened price variants for zero-latency storefront reads. Checkout validates the cart's price against the authoritative price_matrix_version_id — mismatches trigger a hard reject and cart refresh. See costing-pricing-separation ADR.

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique price record
tenant_idUUIDNot NullSoft FK to backoffice.operators
tour_offering_idUUIDForeign Key (tour_offerings.id), Not NullParent offering
price_matrix_version_idUUIDNot NullSoft FK to backoffice.price_matrices — exact version used for checkout validation
channelVARCHARNot Null, Default: DEFAULTSales channel
variantsJSONBNot NullFlattened price variants: [{ room_type, demographic, gross_price, net_price, tax_amount }]
list_priceDECIMALBase variant gross price for quick display
currencyVARCHARNot Null, Default: EUR
synced_atTIMESTAMPTZNot NullLast sync timestamp from PriceMatrixPublished event

Architectural Notes

NOTE

Boarding point ownership (resolved): Boarding point data lives in a single Backoffice table: boarding_point_library (operator-level pickup locations with optional door pickup per stop). Templates reference library items via template_boarding_point_assignments, which also controls per-template origin designation and surcharge overrides. At publish time, the TripPublished event carries the resolved boarding point catalog, and Commerce denormalizes it as available_boarding_points JSONB on tour_offerings. See boarding-points.md for the full data model and adr-001-boarding-point-strategy.md for the cross-context pattern.

Internal documentation — Busflow