Busflow Docs

Internal documentation portal

Skip to content
Reviewed 17 May 2026

Backoffice & Catalog Domain (schema: backoffice) โ€‹

The root authority for business configuration, operational staff, abstract product definitions, third-party inventory, CRM, and all financial planning/quoting.

โ†’ Hub: See domain-model.md for the bounded context map and cross-boundary integration surface.


Entity Relationship Diagram โ€‹

100%

Cross-context references (soft FKs, not shown above):

  • PRICE_MATRIX โ†’ publishes to Commerce TOUR_OFFERING via PriceMatrixPublished event
  • TOUR_DEPARTURE โ†’ publishes to Commerce TOUR_OFFERING via TourDeparturePublished event
  • CREW_MEMBER โ†’ referenced by Operations LEG_ASSIGNMENT
  • VEHICLE โ†’ referenced by Operations LEG_ASSIGNMENT

Entity Dictionary โ€‹

  • Operator: The root tenant entity representing a bus tour company. All domain entities reference the operator via tenant_id. Core fields: name, legal_name, address, country, default_locale (e.g., de-DE), default_currency (ISO 4217, e.g., EUR), vat_id (USt-IdNr.), tax_id, and legal_form (e.g., GMBH, UG, EINZELUNTERNEHMEN). Status lifecycle: ONBOARDING (post-signup, pre-first-login) โ†’ ACTIVE โ†’ SUSPENDED (non-payment or manager action) โ†’ CHURNED (voluntary cancellation or GDPR deletion request). subscription_tier gates feature limits (enforced at application layer via X-Hasura-Plan). [planned] extensions: brand_config (JSONB: { logo_url, primary_color, accent_color }), bank_details (JSONB: { iban, bic, account_holder } โ€” for SEPA payouts and DATEV export). See tenant-provisioning ADR for the signup-to-first-login flow.
  • TenantSubscription: Billing plan state per operator. Holds the plan_id (e.g., CORE, PRO, ENTERPRISE) which the Nhost custom claims webhook uses to inject the X-Hasura-Plan into JWT session variables. Operates on a two-layer billing model (see PRODUCT_payments-and-billing.md ยง3): Lago handles the metered usage and invoicing, while Stripe handles the actual payment collection. Therefore, this entity must track identifiers for both the metering engine (Lago) and the payment collector (Stripe).
  • SubscriptionTier: The abstract level of service (e.g., CORE, PRO, ENTERPRISE) defining the feature access gates for an operator. Enforced at the application layer via X-Hasura-Plan.
  • User (Nhost auth.users): Authentication identity for all actors who log into the Busflow platform (admins, dispatchers, drivers). Managed entirely by Nhost Auth โ€” no separate users table exists in the Backoffice schema. Platform-level role (auth.users.default_role): user or busflow_staff. The user_tenant_assignments.default_role (MANAGER, DISPATCHER, DRIVER) determines tenant-level access, with additional capabilities grantable via user_access_grants for small operators where one person fills multiple roles. A single user can belong to multiple Operators. Deactivation: auth.users.disabled = true (Nhost Admin SDK). See multi-tenant-jwt-session ADR.
  • CrewMember: Operational staff record for drivers and guides. Carries role (DRIVER, GUIDE, DRIVER_GUIDE), contact fields (phone, email), and basic license data. Linked to Nhost auth.users for authentication via user_id FK (nullable โ€” you can register crew members before the system provisions their login account). Status lifecycle: ACTIVE โ†’ INACTIVE โ†’ TERMINATED. Granular qualifications tracked via CrewQualification. Emits CrewMemberStatusChanged โ†’ Operations.
  • CrewQualification: Typed, expiry-tracked professional certifications held by a CrewMember (e.g., Module 95, ADR, border visa, digital tachograph card). Status lifecycle: VALID โ†’ EXPIRING_SOON (Hasura Scheduled Trigger) โ†’ EXPIRED / REVOKED. Emits QualificationExpiring โ†’ Communications. The system tracks the DIGITAL_TACHOGRAPH_CARD type but does not dispatch-block by default โ€” it becomes a hard block only when the operator enables the TACHOGRAPH integration (Phase 2).
  • CrewAbsence: Leave tracking for CrewMember. Types: vacation, sick, rest day, training. Status lifecycle: REQUESTED โ†’ APPROVED / REJECTED. Only APPROVED absences block dispatch. Emits CrewAbsenceApproved โ†’ Operations.
  • Vehicle: Physical fleet assets with vehicle_class (coach, minibus, van, double-decker), transmission_type, current_mileage_km, and a seat_map_layout JSONB (the schema documents the structure). Status lifecycle: ACTIVE โ†’ IN_MAINTENANCE โ†’ DECOMMISSIONED. Emits VehicleStatusChanged โ†’ Operations.
  • VehicleInspection: Scheduled regulatory inspection tracking (HU, SP, tachograph calibration, UVV). Safety-critical overdue inspections auto-set blocks_dispatch = true. Status lifecycle: PENDING โ†’ COMPLETED / OVERDUE. Emits VehicleInspectionOverdue โ†’ Communications.
  • Supplier & Allotment: Manages third-party relationships and reserved inventory blocks (e.g., 20 hotel rooms, ferry slots) essential for executing multi-day tours. Each Allotment carries a currency field (ISO 4217) to support multi-currency procurement as required by the FX engine, and a status lifecycle: ACTIVE (negotiated and available) โ†’ CONSUMED (fully allocated to departures) โ†’ EXPIRED (past end_date) / CANCELLED. Within the Kalkulations-Engine, the Supplier entity acts as the decisive discriminator between Fremdleistung (third-party procurement โ†’ subject to ยง 25 UStG margin taxation) and Eigenleistung (own services โ†’ standard VAT). Every cost line item on the CostingSheet references its originating Supplier to enable automatic tax strategy resolution.
  • Reseller: Represents business clients (travel agencies, institutional clients, tourism boards) whose functional role is to resell or procure the operator's inventory โ€” whether brokering seats on scheduled TourOfferings or requesting custom CharterQuotes for private groups. Distinguished by reseller_type enum: AGENCY (commission-based sales channel) or INSTITUTIONAL (schools, clubs, public bodies, companies, or other organized groups buying a custom service).
  • PersonProfile: The central CRM record for any person the operator has a relationship with โ€” booker, traveler, or both. Unifies interaction histories, dietary needs, and preferred seating across multiple bookings. The system creates a PersonProfile when a person first appears as either a booker or a passenger on any booking. De-duplication matches by email or phone number. Profiles without contact identifiers (e.g., children) cannot de-duplicate and may accumulate duplicate records โ€” manual merge is the operator's responsibility via the CRM interface. Traveler-specific fields (dietary_needs, date_of_birth) are nullable and only populated when relevant โ€” even for travelers, dietary needs are optional (not every tour type cares). DOB sync direction: On BookingConfirmed, the PersonProfile updater writes back the Passenger's date_of_birth to PersonProfile.date_of_birth โ€” the most recent booking input wins. If a Passenger row omits DOB (domestic tour), the system does not overwrite an existing PersonProfile DOB. Border document fields (document_number, nationality) are deliberately absent โ€” these are point-in-time travel credentials that belong on the booking-scoped Commerce Passenger entity. Passport numbers expire and renew between bookings; the Passenger row captures the document the traveler carries on a specific departure. For returning travelers, the UI pre-fills border fields from the most recent Passenger row linked via person_profile_id as a read-only suggestion. See ADR-037.
  • TourDeparture: A concrete, scheduled departure of a TourTemplate โ€” e.g., "Nordsee 7-Tage, 15 June 2026". Holds all date-specific operational context: start_date, end_date, a cloned CostingSheet, and linked BoardingPoints. The TourDeparture is the Backoffice staging entity that, when published, creates/updates the Commerce TourOffering via a TourDeparturePublished event. Status lifecycle: DRAFT โ†’ READY โ†’ PUBLISHED โ†’ COMPLETED / CANCELLED.
  • BoardingPoint (Library Item): An operator-level pickup location (Zustiegsstelle) maintained in a reusable library. Carries name, address, geo_coordinates, zone_label (implicit grouping), surcharge, and optional door pickup configuration (door_pickup_available, door_pickup_surcharge, door_pickup_radius_km). Templates reference library items via template_boarding_point_assignments, which controls per-template origin designation (is_origin), surcharge overrides, and door pickup toggles. Commerce maintains a denormalized read projection (available_boarding_points JSONB on TourOffering) synced via TourDeparturePublished events. See boarding-points.md and adr-001-boarding-point-strategy.md.
  • AncillaryCatalogItem: An operator-level library of bookable extras (insurance, seat upgrades, luggage, excursions, meals). Each item carries a behavioral type (INSURANCE, SEAT_UPGRADE, LUGGAGE, EXCURSION, MEAL, OTHER) and an operator-defined label. These types are deliberately behavior-driven: they govern refund assumptions, reporting buckets, fulfillment hints, and invoice line grouping. Room categories and single-room supplements remain in PriceMatrix / PricingConfig, not ancillaries. Templates reference catalog items via template_ancillary_assignments, which supports per-template price overrides and an included_by_default toggle. Follows the same centralized-overridable cascade as boarding points and deposit config. Commerce receives the resolved catalog via TourDeparturePublished events as available_ancillaries JSONB on TourOffering. See ancillary-catalog.md.
  • NotificationTemplate: Operator-configurable message templates for automated communications (booking confirmations, pre-departure reminders, delay broadcasts). Carries channel (EMAIL, WHATSAPP, PUSH), trigger_event, subject, body_template with variable interpolation, and locale. Valid trigger_event values correspond to registered Hasura Event Trigger and Scheduled Trigger names โ€” see workflow-orchestration.md for the canonical event catalog.
  • CostingSheet (Vorkalkulation): The universal cost planning engine. Contains projected fixed costs, variable costs (leveraging Allotment data), FX risk buffers, and break-even metrics. Does not contain selling prices โ€” those live on PriceMatrix. Carries a status lifecycle: DRAFT (editable, under construction) โ†’ CALCULATED (engine has produced valid cost aggregates and triggered PriceMatrix generation) โ†’ LOCKED (immutable โ€” triggered by the BookingConfirmed event on first commercial commitment per ADR-009, defended by ยง 651a BGB; today this event fires on DEPOSIT_PAID, but cost-lock is modeled as an independent consumer of BookingConfirmed and is unaffected if other consequences are re-bound to a different trigger โ€” see domain-commerce.md ยง Booking State vs. event decoupling). LOCKED means the system freezes costs, not prices. Edits to a LOCKED sheet require cloning into a new DRAFT revision. See costing-pricing-separation ADR.
  • PriceMatrix (Preisgestaltung): The market-driven selling price entity. Derives its baseline from a CostingSheet but evolves independently based on yield and channel strategies. Contains margin_config (margin is a pricing rule, not a cost), resolved price variants[], and list_price. Supports a 1:N relationship โ€” one CostingSheet can generate multiple PriceMatrices per sales channel. Uses immutable append versioning (every mutation creates a new version). Lifecycle: DRAFT (generated, under review) โ†’ PUBLISHED (pushed to Commerce via PriceMatrixPublished event) โ†’ ARCHIVED (superseded by a new version). Pricing hierarchy: Level 1 (Base Price from costs + margins), Level 4 (Manual Override โ€” "Dispatcher's Veto"). [planned โ€” Phase 2]: Level 2 (Channel Rules), Level 3 (Algorithmic Yield Rules). See costing-pricing-separation ADR and pricing-screen.md. Each variant carries a complete applied_conditions[] audit trail.
  • TourTemplate: The abstract commercial product โ€” the reusable blueprint for a tour type. Uses pgvector for ai_embedding to enable AI-assisted parsing of unstructured PDF itineraries into structured data. References a baseline CostingSheet. Scheduling a concrete departure creates a TourDeparture. Carries a status lifecycle: DRAFT (incomplete, not yet publishable) โ†’ ACTIVE (the user can schedule departures from this template) โ†’ ARCHIVED (no new departures, existing ones unaffected). Stores pricing_rules, pricing_config, capacity_rules, and deposit_config as JSONB value objects (see below).
  • PricingRule, PricingConfig, CapacityRule & DepositConfig (Value Objects): Embedded as JSONB on TourTemplate, validated at runtime via Valibot schemas (consistent with the CostingSheet value object pattern). PricingRule (input VO) structure: { demographic: string, age_min, age_max, discount_type: 'PERCENTAGE' | 'ABSOLUTE', discount_value, label } โ€” defines operator-configurable demographic segments (e.g., 'ADULT', 'CHILD', 'SENIOR', 'STUDENT', 'INFANT') and their discount. 'ADULT' is the conventional base segment (no discount). At PriceMatrix generation, PricingRules become AppliedCondition output VOs in each variant's audit trail. PricingConfig (input VO) structure: { room_surcharge, includes_accommodation, season_config: SeasonConfig[], early_bird_config: EarlyBirdTier[] } โ€” configures room surcharges, seasonal tiers, and early-bird discount tiers. CapacityRule structure: { strategy: 'FREE_SALE' | 'LIMITED' | 'REQUEST_ONLY', max_capacity, overbooking_buffer_pct }. DepositConfig structure: { percentage: number, type: 'PERCENTAGE' | 'FIXED', min_amount: number | null }. Deposit rules require central defaults and immutable snapshots: operator/system defaults define the baseline; a concrete TourDeparture should snapshot the resolved deposit rule so later default changes cannot silently alter a published tour; each Booking must snapshot the deposit terms applied at checkout for audit and legal stability. A template-level deposit_config override is a possible convenience, but should be validated before treating it as a core invariant. See PRODUCT_mollie-integration.md ยง3 for the cascading resolution logic. Each TourDeparture inherits all VOs at creation, and the user can override them per departure. PricingRule and PricingConfig are deep-copied (snapshotted) onto the PriceMatrix at generation time for historical audit.
  • CharterQuote: A B2B commercial wrapper calculating dynamic Leerkilometer (empty runs) and toll costs before converting to a scheduled TourDeparture. The associated CostingSheet.variable_costs JSONB stores the calculated Leerkilometer distance and itemized toll cost segments under the keys leerkilometer_km, leerkilometer_cost, and toll_segments[].

The Costing & Pricing Engine (Vorabkalkulation) โ€‹

Full specification: See kalkulations-engine.md for the complete engine specification, including the two-phase pipeline (CostingSheet โ†’ PriceMatrix), data structures, TypeScript variant/condition interfaces, composition algorithm, edge states, and embedded value objects.

Event contracts: See event-contracts-pricing.md for Backoffice โ†” Commerce payload schemas and delivery contracts.

Related docs: costing-pricing-separation ADR ยท pricing-screen.md ยท tax-engine-protocol.md

The CalculationEngine is a central Domain Service in the Backoffice context. It operates in two sequential phases:

  1. Phase 1 โ€” Cost Calculation: Aggregates Eigenleistungen (own costs) and Fremdleistungen (third-party procurement via Supplier/Allotment), applies FX risk buffers, validates break-even, and resolves tax strategy (STANDARD_VAT vs. MARGIN_SCHEME_25). Output: a CostingSheet with total_net_cost and status = CALCULATED.

  2. Phase 2 โ€” Price Generation: Applies margin_config rules to the cost base, generates the list_price, then expands into a full PriceMatrix with variants[] covering all combinations of room type ร— demographic ร— season ร— early-bird tier. Each variant carries a complete applied_conditions[] audit trail.

Three operating modes: (1) Commerce Cache Push for catalog business, (2) Yield Management Interface for manual price overrides, (3) Real-Time Calculation for B2B charter quotes.


Aggregates โ€‹

Each aggregate defines a transactional consistency boundary. The aggregate root controls access to all children and enforces invariants. Cross-aggregate references follow ADR-036.

Operator โ€‹

RoleEntity
RootOperator
ChildOperatorSettings (1:1)
ChildOperatorIntegrations
ChildTenantSubscription (1:1)

Invariants: Exactly one settings/subscription row. Status: ONBOARDING โ†’ ACTIVE โ†’ SUSPENDED โ†’ CHURNED. At most one integration per type. Events: TenantProvisioned, TenantActivated, TenantSuspended, TenantChurned.

TourTemplate โ€‹

RoleEntity
RootTourTemplate
ChildTemplateBoardingPointAssignment
ChildTemplateAncillaryAssignment
VOPricingRule[], PricingConfig, CapacityRule[], DepositConfig (JSONB)

Invariants: At most one is_origin per template. Status: DRAFT โ†’ ACTIVE โ†’ ARCHIVED. Cross-aggregate refs: boarding_point_id and ancillary_catalog_item_id on assignments are soft FKs (library items have independent archival lifecycle).

TourDeparture โ€‹

Single-entity aggregate. Concrete scheduled instance of a TourTemplate.

Invariants: Status: DRAFT โ†’ READY โ†’ PUBLISHED โ†’ COMPLETED / CANCELLED. Publishing emits TourDeparturePublished. Cross-aggregate refs: tour_template_id (hard FK โ€” stable reference), costing_sheet_id (hard FK โ€” stable reference).

CostingSheet โ€‹

Single-entity aggregate with embedded JSONB VOs (procurement_items[], variable_costs, fx_risk_buffer).

Invariants: Status: DRAFT โ†’ CALCULATED โ†’ LOCKED. LOCKED triggers automatically on first DEPOSIT_PAID. Edits to LOCKED require cloning.

PriceMatrix โ€‹

Single-entity aggregate with embedded JSONB VOs (variants[], margin_config, pricing_rules_snapshot, pricing_config_snapshot).

Invariants: Status: DRAFT โ†’ PUBLISHED โ†’ ARCHIVED. Immutable append versioning. 1:N per CostingSheet. Events: PriceMatrixPublished, SeasonPricingFinalized. Cross-aggregate refs: costing_sheet_id and tour_departure_id are soft FKs (ADR-006 โ€” independent lifecycle). superseded_by is a hard FK (self-referential version chain).

CrewMember โ€‹

RoleEntity
RootCrewMember
ChildCrewQualification
ChildCrewAbsence

Invariants: Status: ACTIVE โ†’ INACTIVE โ†’ TERMINATED. Only ACTIVE members in dispatch. Only APPROVED absences block dispatch. VALID qualifications required for dispatch eligibility. Events: CrewMemberStatusChanged, CrewAbsenceApproved, QualificationExpiring.

Vehicle โ€‹

RoleEntity
RootVehicle
ChildVehicleInspection
VOSeatMapLayout (JSONB)

Invariants: Status: ACTIVE โ†’ IN_MAINTENANCE โ†’ DECOMMISSIONED. Only ACTIVE in dispatch. Overdue inspections block dispatch. Events: VehicleStatusChanged, VehicleInspectionOverdue, VehicleInspectionScheduled, VehicleInspectionCompleted.

Supplier โ€‹

Single-entity aggregate. Discriminates Fremdleistung vs. Eigenleistung for tax strategy resolution.

Allotment โ€‹

Single-entity aggregate. Reserved inventory blocks from a supplier.

Invariants: Status: ACTIVE โ†’ CONSUMED / EXPIRED / CANCELLED. Cross-aggregate refs: supplier_id is a soft FK (independent lifecycle โ€” supplier archival must not cascade).

BoardingPointLibrary โ€‹

Single-entity aggregate. Operator-level reusable pickup location.

AncillaryCatalogItem โ€‹

Single-entity aggregate. Operator-level reusable bookable extra.

Reseller โ€‹

Single-entity aggregate. B2B partner. reseller_type: AGENCY or INSTITUTIONAL.

PersonProfile โ€‹

Single-entity aggregate. CRM record for any person the operator manages โ€” booker, traveler, or both. De-duplicated by contact identifier (email or phone).

NotificationTemplate โ€‹

Single-entity aggregate. Operator-configurable message template.

Clock authorities for scheduled reminders: The PRE_DEPARTURE_REMINDER cron fires from Backoffice TourDeparture.start_date (planning clock). The Freshness Gate validates against Operations ServiceLeg.status before dispatch (execution truth). Template variables resolve boarding_time from Operations ServiceLeg.scheduled_departure for the PICKUP-typed leg, falling back to Backoffice BoardingPoint time if no ServiceLeg exists yet.

CharterQuote โ€‹

Single-entity aggregate. B2B quote wrapper.

Invariants: Status: DRAFT โ†’ SENT โ†’ ACCEPTED / REJECTED. Can convert to TourDeparture. Cross-aggregate refs: reseller_id (hard FK โ€” stable reference), costing_sheet_id (hard FK โ€” stable reference).

Infrastructure Entities (not aggregates) โ€‹

change_events, import_jobs + import_error_rows, tenant_scrub_logs, fx_reference_rates, user_tenant_assignments, user_access_grants.

Internal documentation โ€” Busflow