Busflow Docs

Internal documentation portal

Skip to content

Master Version - Unified Domain Model: BusFlow SaaS ​

This document provides the authoritative, finalized domain model for the BusFlow platform. It utilizes a Modular Monolith architecture, grouping highly cohesive Bounded Contexts into four database schemas: Backoffice, Commerce, Operations, and Communications (a Shared Core Domain serving the other three). A fifth context β€” Customer Intelligence β€” targets Phase 3 (see Β§5).

Core Architectural Principles:

  1. Event-Driven Decoupling: To prevent database deadlocks and tight coupling, contexts communicate primarily via asynchronous Domain Events (e.g., BookingConfirmed, IssueReported, DutyViolation).
  2. Soft Cross-Schema References: The system strictly enforces hard foreign key constraints within a schema, but cross-schema relationships use soft ID referencing (e.g., the Commerce schema stores service_leg_id as an integer without a hard database-level constraint to the Operations schema).

Unified Entity Relationship Diagram ​

mermaid
erDiagram
    %% ==========================================
    %% BACKOFFICE CONTEXT (Master Data, Planning & B2B)
    %% ==========================================
    OPERATOR ||--o{ USER : "employs (auth)"
    OPERATOR ||--o{ CREW_MEMBER : "employs (ops)"
    OPERATOR ||--o{ VEHICLE : "owns"
    OPERATOR ||--o{ PASSENGER_PROFILE : "manages CRM"
    OPERATOR ||--o{ SUPPLIER : "contracts"
    OPERATOR ||--o{ RESELLER : "partners with"
    OPERATOR ||--o{ NOTIFICATION_TEMPLATE : "configures"
    
    SUPPLIER ||--o{ ALLOTMENT : "provides (hotels/ferries)"
    
    OPERATOR ||--o{ TOUR_TEMPLATE : "creates"
    OPERATOR ||--o{ CHARTER_QUOTE : "issues"
    TOUR_DEPARTURE ||--o{ BOARDING_POINT : "defines stops"
    
    %% Costing Engine (pure costs)
    COSTING_SHEET |o--o| TOUR_TEMPLATE : "baseline budget"
    COSTING_SHEET ||--|| CHARTER_QUOTE : "custom budget"
    COSTING_SHEET |o--o| TOUR_DEPARTURE : "instance budget"
    ALLOTMENT ||--o{ COSTING_SHEET : "factors into"
    
    %% Pricing Engine (selling prices)
    COSTING_SHEET ||--o{ PRICE_MATRIX : "generates"
    PRICE_MATRIX ||--o| TOUR_OFFERING : "publishes to"
    
    TOUR_TEMPLATE ||--o{ TOUR_DEPARTURE : "schedules"
    TOUR_DEPARTURE ||--o| TOUR_OFFERING : "publishes to"
    CHARTER_QUOTE ||--o| TOUR_DEPARTURE : "converts to"
    RESELLER ||--o{ CHARTER_QUOTE : "requests"

    %% ==========================================
    %% OPERATIONS CONTEXT (Execution & Fleet)
    %% ==========================================
    TOUR_DEPARTURE ||--|{ SERVICE_LEG : "divided into"
    SERVICE_LEG ||--o{ LEG_ASSIGNMENT : "assigned via"
    
    LEG_ASSIGNMENT }o--|| VEHICLE : "uses"
    LEG_ASSIGNMENT }o--|| CREW_MEMBER : "operated by"
    
    CREW_MEMBER ||--o{ CREW_DUTY_LOG : "records via IoT"
    SERVICE_LEG ||--|{ ROUTE_WAYPOINT : "follows"
    VEHICLE ||--|{ TELEMETRY_POINT : "emits"
    
    VEHICLE ||--o{ ISSUE_REPORT : "logged against"
    CREW_MEMBER ||--o{ ISSUE_REPORT : "reports"
    SERVICE_LEG ||--o{ INCIDENT : "disrupted by"
    ISSUE_REPORT ||--o{ INCIDENT_ISSUE_REPORT : "linked via"
    INCIDENT ||--o{ INCIDENT_ISSUE_REPORT : "linked via"
    
    CREW_MEMBER ||--o{ EXPENSE_RECEIPT : "submits actuals"
    SERVICE_LEG ||--o{ ONBOARD_SALE : "generates actuals"
    
    TICKET ||--o{ ONBOARD_SALE : "billed to seat"

    %% ==========================================
    %% COMMERCE & FINANCE CONTEXT (Sales & Tax)
    %% ==========================================
    TOUR_OFFERING ||--o{ CHECKOUT_SESSION : "initiates"
    TOUR_OFFERING ||--o{ BOOKING : "receives"
    SERVICE_LEG ||--o{ SEAT_RESERVATION : "manages capacity"
    
    RESELLER ||--o{ BOOKING : "brokers"
    CHECKOUT_SESSION |o--o| BOOKING : "converts to"
    
    BOOKING ||--|{ PAYMENT : "processes (via Mollie)"
    BOOKING ||--o{ ANCILLARY : "adds"
    BOOKING ||--|{ PASSENGER : "includes"
    PASSENGER }o--|| PASSENGER_PROFILE : "links to"
    PASSENGER }o--o| BOARDING_POINT : "boards at (soft ref)"
    SEAT_RESERVATION |o--o| PASSENGER : "reserves for"
    PASSENGER ||--o{ TICKET : "issued to"
    
    TOUR_OFFERING ||--|| FINANCIAL_LEDGER : "aggregates actuals (Nachkalkulation)"
    FINANCIAL_LEDGER ||--o{ TAX_LEDGER_ENTRY : "calculates tax"
    FINANCIAL_LEDGER ||--o{ INVOICE : "generates"

    %% ==========================================
    %% COMMUNICATIONS CONTEXT (Shared Core Domain)
    %% ==========================================
    OPERATOR ||--o{ CHANNEL_ACCOUNT : "provisions"
    OPERATOR ||--o{ CONTACT : "tracks"
    CONTACT ||--o{ CONVERSATION : "participates in"
    CONVERSATION ||--o{ MESSAGE : "contains"
    MESSAGE }o--|| CHANNEL_ACCOUNT : "sent via"
    CONVERSATION }o--o| BOOKING : "linked to (soft ref)"
    CONVERSATION }o--o| SERVICE_LEG : "linked to (soft ref)"
    CONVERSATION }o--o| INVOICE : "linked to (soft ref)"

Schema & Entity Dictionary ​

Multi-Tenancy Strategy: Every aggregate root carries a tenant_id (UUID) referencing the owning Operator. Hasura permission rules enforce row-level isolation by extracting x-hasura-tenant-id from the JWT and matching it against tenant_id. The system assigns users to specific tenants via a user_tenant_assignments table, preventing unauthorized cross-tenant data access. See domain-driven-design.md for enforcement rules.

1. Backoffice & Catalog Schema (schema: backoffice) ​

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

  • 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). [planned] extensions: brand_config (JSONB: { logo_url, primary_color, accent_color }), bank_details (JSONB: { iban, bic, account_holder } β€” for SEPA payouts and DATEV export), subscription_tier (gates feature limits, enforced at application layer). See tenant-provisioning ADR for the signup-to-first-login flow.
  • 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, corporate accounts, tourism boards) whose functional role is to resell 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 CORPORATE (custom procurement client).
  • PassengerProfile: The central CRM record for a traveler, unifying interaction histories, dietary needs, and preferred seating across multiple bookings. De-duplication uses the contact identifier from the original booking (email or phone number) to match returning travelers to existing profiles.
  • 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 TripPublished 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 TripPublished events. See boarding-points.md and adr-001-boarding-point-strategy.md.
  • AncillaryCatalogItem: An operator-level library of bookable extras (insurance, room upgrades, excursions, meals). Each item carries a behavioral type (INSURANCE, UPGRADE, EXTRA_LUGGAGE, EXCURSION, MEAL, OTHER) and an operator-defined label. 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 TripPublished events as available_ancillaries JSONB on TourOffering. See ancillary-catalog.md.
  • NotificationTemplate: Operator-configurable message templates for automated communications (booking confirmations, pre-trip 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 β€” automatically triggered when the first booking reaches DEPOSIT_PAID against the linked TourDeparture, per ADR-009). 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 } β€” overrides the tenant-level default deposit rules for this template. 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 operational trip. 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[].

2. Commerce & Finance Schema (schema: commerce) ​

The conversion and accounting engine, focused on B2C/B2B sales, capacity holds, cross-border manifest data, and generalized taxation logic based on actuals.

  • TourOffering: The sellable, Commerce-owned projection of a published TourDeparture. Created/updated when Backoffice publishes a TourDeparture via the TripPublished event. References the source TourDeparture and TourTemplate via soft FKs. Carries denormalized display data. Pricing comes from TourOfferingPrice β€” a read-model projection of the Backoffice PriceMatrix, synced asynchronously via PriceMatrixPublished events. Lifecycle status: SCHEDULED, SOLD_OUT, COMPLETED, CANCELLED (Note: DRAFT and READY states belong natively to TourDeparture in Backoffice and are not projected to Commerce until published).
  • TourOfferingPrice (Read-Model): A flattened Commerce-side projection of the Backoffice PriceMatrix. Stores price_matrix_version_id (soft FK to backoffice.price_matrices), channel, and variants (JSONB). Synced via PriceMatrixPublished events. Checkout validates the cart's price against the authoritative price_matrix_version_id β€” mismatches trigger a hard reject and cart refresh.
  • CheckoutSession: A short-lived entity tracking a user’s purchase intent before the system confirms a Booking. Captures the selected TourOffering, passenger count, seat holds, and chosen boarding point. Status lifecycle: ACTIVE β†’ EXPIRED (TTL elapsed) or CONVERTED (successful payment). Converts to a Booking upon successful payment. Enables abandoned cart recovery: session expires after TTL (default 30 minutes) β†’ Hasura Scheduled Trigger sets status = 'EXPIRED' β†’ emits CheckoutAbandoned event β†’ Communications context executes re-engagement workflow (Email at +1h, WhatsApp at +24h). See PRODUCT_mollie-integration.md Β§4 for the full checkout pipeline.
  • Booking: The primary transactional container bundling requested products and managing checkout states. The system defines the booking lifecycle through a strict state machine: DRAFT β†’ PENDING_PAYMENT β†’ DEPOSIT_PAID β†’ FULLY_PAID β†’ COMPLETED β†’ CANCELLED / REFUNDED / NO_SHOW.
  • Passenger: The individual traveler on a booking. Crucially distinguishes between the Primary Billing Contact and Accompanying Travelers, which is essential for accurate cross-border documentation and border manifests. References a boarding_point_id (soft FK to Backoffice) for the chosen pickup location.
  • Payment: Granular tracking via Mollie Marketplaces, where the system handles split transactions like deposit (Anzahlung) and final payment (Restzahlung). Uses Mollie's delayed capture and marketplace routing for multi-party payouts to operators. Supports SEPA, PayPal, Apple/Google Pay, Klarna (BNPL), iDEAL, and credit cards. Records the actual payment_method used (populated from Mollie webhook). See PRODUCT_mollie-integration.md for the complete integration spec including webhook-to-state mapping.
  • Ancillary: Upsells added to a booking. The type field defines the behavioral category (INSURANCE, UPGRADE, EXTRA_LUGGAGE, EXCURSION, MEAL, BOARDING_SURCHARGE, OTHER) which governs system behavior (refund rules, reporting). BOARDING_SURCHARGE is auto-created by Commerce when a passenger selects a boarding point with a surcharge. The label field is operator-defined (the passenger-facing name). Each ancillary has an independent lifecycle (ACTIVE, CANCELLED, REFUNDED) to support partial cancellation. Carries an optional catalog_item_id (soft FK to backoffice.ancillary_catalog_items) for traceability to the operator's catalog; null for system-generated items like boarding surcharges.
  • SeatReservation: Time-locked capacity holds mapped against the operational ServiceLeg. Carries seat_identifier (matching a key in the vehicle’s seat_map_layout JSONB), hold_expires_at (aligned with checkout session TTL, default 30 minutes β€” see ADR-013), and status enum (HELD, CONFIRMED, RELEASED). A Hasura Scheduled Trigger (seat_hold_cleanup) runs every 60s to release expired holds. When a vehicle swap occurs, reservations are re-mapped to equivalent seat positions on the new layout.
  • Ticket (Fulfillment Artifact): The digital fulfillment and boarding pass. Owned by Commerce (it is a commercial fulfillment artifact of a confirmed booking). Operations reads the ticket via a cross-schema soft reference or denormalized read model to track check_in_status via qr_hash validation in the driver's offline app.
  • FinancialLedger (Nachkalkulation): The per-departure profitability ledger. The system auto-creates one FinancialLedger per TourOffering when the first booking reaches DEPOSIT_PAID. It aggregates actual booking revenues (realized_revenue = sum of Payment.amount with status = COMPLETED) and actual operational expenses (realized_expense = sum from ExpenseSubmitted and OnboardSaleRecorded domain events consumed from Operations). Snapshots two Soll baselines at creation: planned_cost from CostingSheet.total_net_cost and planned_revenue from the active PriceMatrix (Ξ£ variant prices Γ— expected pax at CapacityRule.max_capacity; version tracked via planned_price_matrix_version_id). Computes three delta columns: cost_delta = realized_expense βˆ’ planned_cost, revenue_delta = realized_revenue βˆ’ planned_revenue, margin_delta = (realized_revenue βˆ’ realized_expense) βˆ’ (planned_revenue βˆ’ planned_cost). Carries costing_sheet_id (soft FK) for Soll/Ist baseline and currency (ISO 4217). Status lifecycle: OPEN (accepting mutations from booking/expense events) β†’ CLOSED (period-locked, the system finalizes all actuals via the closed_at timestamp). See ADR-002.
  • TaxLedgerEntry: A generalized entity utilizing a TaxStrategyType to calculate the correct tax burden based on ledger actuals. Relationship is 1:N β€” a single FinancialLedger can produce multiple TaxLedgerEntries when a departure mixes tax strategies (e.g., one MARGIN_SCHEME_25 entry for the tour margin and one STANDARD_VAT entry for onboard sales). Persists the four mandatory Β§ 25 Abs. 5 UStG recording fields as immutable columns. See ADR-012 and tax-engine.md.
  • Invoice: Consolidates billing information and integrates e-invoicing standards. The system generates invoices as PDFs directly from aggregated TaxLedgerEntry and Booking data without a persistent invoice_line_items table.
  • LedgerPeriodLock: Prevents mutation of financial records (FinancialLedger, Invoice, TaxLedgerEntry) after the user closes a reporting period or the system exports it to DATEV. Lock types: EXPORT (irreversible, auto-created on DATEV export) and MANUAL (operator-triggered month-end close, admin-unlockable with audit trail). See period-lock.md.
  • InvoiceCancellation (Storno): GoBD-compliant cancellation record linking a voided invoice to its counter-document (Stornorechnung) and optional replacement invoice. The system never modifies the original invoice β€” it creates a new counter-invoice with negative amounts. See storno-workflow.md.
  • Infrastructure Entities: The physical schema includes lower-level tables (ReconciliationUpload, ReconciliationEntry) supporting DATEV BWA/SuSa import and Soll/Ist comparison at the account-code level. These are import/comparison utilities without domain lifecycle.

3. Operations Schema (schema: operations) ​

The execution layer handling real-world logistics, fleet assignments, hardware IoT integrations, offline app synchronization, and fulfillment validation.

  • ServiceLeg: The physical, continuous operation of a vehicle between an origin and destination. A single TourDeparture consists of multiple ServiceLegs. Operations creates ServiceLegs when the TripPublished event arrives from Backoffice (see ADR-018). Carries tour_departure_id (soft FK to Backoffice), leg_type enum (PICKUP, TRANSIT, TRANSFER, DROPOFF, REPOSITIONING), and actual_start/actual_end timestamps for Soll/Ist comparison. A PICKUP-typed ServiceLeg is the Operations-side representation of a Backoffice BoardingPoint (1:1) β€” see ADR-001. Lifecycle: SCHEDULED β†’ ACTIVE (emits ServiceLegStarted) β†’ COMPLETED (emits ServiceLegCompleted) or DELAYED (emits ServiceLegDelayed) β†’ COMPLETED/CANCELLED (emits ServiceLegCancelled). DELAYED can recover to ACTIVE (emits ServiceLegDelayResolved). Full state machine with guards, actors, and cancellation resolution scenarios in schema-operations.md Β§ServiceLeg State Machine.
  • LegAssignment: The dispatching link tying a specific ServiceLeg to a Vehicle and a CrewMember. Carries status (CONFIRMED, RELEASED) and assigned_at timestamp for audit and multi-dispatcher conflict resolution.
  • CrewDutyLog: The immutable ledger recording driving and rest times. Carries service_leg_id (nullable) to correlate duty events with active legs. Phase 1 uses only the single-timestamp model (log_time) for the 11h daily rest heuristic. Phase 2 will introduce a DutyActivity entity with duration-based logging (start_time, end_time, duration_minutes) and a source_type discriminator (TOUR_LEG, LINE_ROUTE, CHARTER, TACHOGRAPH_IMPORT, SELF_DECLARATION) to decouple compliance evaluation from the tour-specific ServiceLeg pipeline. CrewDutyLog remains for operational correlation; DutyActivity supersedes it as the compliance data source. Phase 2 reserves the tachograph_data JSONB column for Gen-2 Smart Digital Tachograph IoT integration. See EU-561 research Β§6 for the full design intent.
  • TelemetryPoint: High-frequency GPS signals emitted by the vehicle. Carries service_leg_id (nullable) to correlate position data with active legs. Phase 2 reserves fuel_level for future use.
  • RouteWaypoint: Planned geographical execution paths with ETA tracking. Carries label (descriptive name, e.g., "KΓΆln Hbf") and waypoint_type enum (BOARDING_STOP, REST_AREA, WAYPOINT, BORDER_CROSSING). BOARDING_STOP waypoints enable the Consumer ETA Tracking endpoint to show "ETA to your stop" β€” see event-contracts-operations.md Β§Consumer ETA Tracking.
  • IssueReport: The raw field log a CrewMember creates and ties to a Vehicle. Captures any observed problem (e.g., cracked mirror, engine warning light). Status lifecycle: OPEN (driver submits) β†’ IN_PROGRESS (Backoffice schedules a VehicleInspection and emits VehicleInspectionScheduled) β†’ RESOLVED (Backoffice completes inspection and emits VehicleInspectionCompleted). When maintenance_urgency = IMMEDIATE, the system emits VehicleMaintenanceRequired β†’ Backoffice. An IssueReport does not automatically create an Incident β€” the dispatcher manually links IssueReports to Incidents via a join table when a vehicle observation escalates into an operational disruption. Supports attachments JSONB for photographic evidence.
  • Incident: The operational disruption tied to a ServiceLeg. Represents a logistical impact (delay, breakdown, passenger issue). Created by the driver from the Driver Hub, or auto-created by the system for telemetry-detected delays (see schema-operations.md Β§ServiceLeg State Machine ACTIVEβ†’DELAYED). Lifecycle: OPEN (emits IncidentCreated with severity and type in payload) β†’ ACKNOWLEDGED (dispatcher sets assigned_to) β†’ IN_PROGRESS (dispatcher acting) β†’ RESOLVED (sets resolved_at, emits IncidentResolved). IncidentCreated is the sole trigger for all passenger broadcasts β€” the Communications consumer routes uniformly by severity: CRITICAL (any type: DELAY, BREAKDOWN, PASSENGER_ISSUE) triggers proactive WhatsApp broadcast to downstream passengers (Journey 2); LOW/MEDIUM triggers dispatch board alert only. System-created DELAY Incidents carry reporter_crew_id=NULL and severity=CRITICAL (ensures entry into the broadcast chain β†’ dispatcher approval gate). BREAKDOWN incidents do NOT directly trigger VehicleMaintenanceRequired β€” vehicle maintenance is the responsibility of IssueReport (see ADR-008). The Driver Hub prompts the driver to file an IssueReport alongside a BREAKDOWN incident. Carries geo_coordinates for the GPS pin location and description for unstructured details.
  • OnboardSale: Point-of-Sale records managing the driver's cash box and dynamic upsells. Carries sale_status lifecycle: ACTIVE β†’ VOIDED (driver or dispatcher cancels before end-of-day, emits OnboardSaleVoided) / REFUNDED (role-gated, emits OnboardSaleRefunded). Tracks crew_member_id (the seller), payment_method (CASH, PAYMENT_LINK, CARD_READER), and quantity. Supports digital payments via payment_status (PAID, PENDING_LINK, FAILED) and checkout_session_id (soft FK to Commerce CheckoutSession for Magic Link flow). PENDING_LINK sales require connectivity β€” the driver app disables payment link creation when offline. Cash sales operate fully offline. Voiding and refunding create GoBD-compliant counter-entries via change_events (scope = GOBD). On sync, emits OnboardSaleRecorded β†’ Commerce FinancialLedger for realized_expense aggregation when payment_status = PAID. Refund authorization is payment-method-scoped: CASH refunds are available to any user with sufficient permissions (MANAGER, DISPATCHER, or DRIVER with can_refund_onboard_sale grant within driver_cash_refund_limit), supporting owner-operators who drive themselves. PAYMENT_LINK/CARD_READER refunds require MANAGER or DISPATCHER role (crosses into Commerce for Mollie refund API). Feature gap: four-eyes refund approval workflow deferred to Phase 2.
  • ExpenseReceipt: AI-parsed OCR data capturing actual operational costs (fuel, tolls, parking) incurred during a ServiceLeg. Carries receipt_image_key for the uploaded photo (storage key, not URL) and ocr_data JSONB for extracted fields. Includes net_amount, vat_amount, and vat_rate (default 0, populated by OCR, verified by dispatcher) for DATEV-compliant accounting. OCR pipeline: PENDING (driver uploads photo) β†’ PARSED (BullMQ OCR worker extracts data via AI service) β†’ VERIFIED (dispatcher approves, emits ExpenseSubmitted β†’ Commerce FinancialLedger) or REJECTED (terminal state β€” dispatcher rejects with rejection_reason, driver creates a new ExpenseReceipt row with replaces_receipt_id linking to the rejected original).
  • BoardingEvent: Records the QR scan check-in of a passenger during boarding. References ticket_id (soft FK to Commerce Ticket) and captures check_in_status (SUCCESS, INVALID, ALREADY_SCANNED, MANUAL_OVERRIDE), qr_hash, and physical baggage metrics. Carries expected_service_leg_id when the system uses MANUAL_OVERRIDE (wrong-stop boarding β€” preserves the audit trail of where the system expected the passenger to board vs. where they actually boarded). The driver app validates QR codes offline against a cached manifest with 5 sequential checks β€” see schema-operations.md Β§Offline Validation Rules. Per-passenger scanning enables accurate BookingNoShow detection per Ticket, not per Booking. This entity replaces the previous ambiguous Ticket dual-ownership β€” Commerce owns the Ticket, Operations owns the BoardingEvent.

Offline Sync & Compliance Strategy ​

  • Offline Conflict Resolution: The Driver Hub (PWA) uses an aggressive offline-caching strategy (IndexedDB/RxDB) providing read-isolated clones of the dispatch manifest. The architecture uses a "Server Wins (Delta Driven)" sync mechanism when the system restores the connection, generating change_events server-side on receipt of synced records. The client sends raw mutations with client-generated idempotency_key UUIDs; the server deduplicates via the sync_idempotency_log table. See offline-sync-protocol.md for the full protocol specification including queue structure, sync API contract, batch semantics, and connectivity detection.
  • Compliance Rules Boundary (EU-561/2006): Complex compliance evaluations (e.g., matching driving hours with EU requirements) intentionally do not occur in the operations physical database schema. CrewDutyLog persists the raw events. In Phase 2, a stateless ComplianceEvaluator domain service in Backoffice will consume DutyActivity records (source-agnostic: tours, scheduled routes, chartering, tachograph imports) and produce per-driver ComplianceStatus (remaining driving budget, rest deficit, violation flags) injected back to the dispatch board asynchronously. This design ensures EU-561 evaluation is reusable across all business areas without coupling to the ServiceLeg β†’ TourDeparture pipeline. See EU-561 research Β§6.

4. Communications Schema (schema: communications) ​

An independent Shared Core Domain providing omnichannel inbox and trigger-based automated messaging to Backoffice, Commerce, and Operations.

  • ChannelAccount: A configured communication endpoint provisioned per tenant via CPaaS providers (Meta Cloud API, Amazon SES, Amazon SNS). Carries channel_type (WHATSAPP, EMAIL, WEBCHAT, SMS), typed provider_config JSONB (shape varies by channel β€” see schema-communications.md), and a lifecycle status (PENDING_VERIFICATION β†’ ACTIVE β†’ SUSPENDED β†’ REVOKED). Each tenant has at most one channel account per type. Tracks the operator's canonical sender identity (E.164 phone number, verified email domain) and an operator-editable display name. Sensitive provider secrets are encrypted at rest via pgsodium. See channel-provisioning-protocol.md for registration flows and lifecycle management.
  • Contact: Unified identity for an external person (customer, driver, vendor) merging cross-channel communication threads. Links to PassengerProfile (Backoffice) via soft FK when the contact is a known traveler. Carries identifiers[] (email addresses, phone numbers) for cross-channel de-duplication.
  • Conversation: The centralized container for a communication thread. Supports agent assignment (claim-based routing), snooze timers, and per-agent unread tracking. Uses exclusive nullable foreign keys instead of polymorphic associations to maintain Hasura GraphQL compatibility:
    • booking_id (Commerce), trip_id (Operations), invoice_id (Backoffice)
    • CHECK (num_nonnulls(booking_id, trip_id, invoice_id) <= 1) β€” enforces that each conversation links to at most one business context
    • This permits the frontend to execute a single GraphQL query fetching an inbox thread with its deeply nested domain context (e.g., live telemetry for a trip or line items for a booking).
  • ConversationReadCursor: Per-agent read position tracking within a conversation. Enables unread count computation via a Hasura computed field. See inbox-protocol.md Β§6.
  • Message: Individual communication payloads linked to a Conversation, a ChannelAccount, and a Contact. Tracks the full delivery lifecycle from dispatch through provider confirmation, with idempotency dedup (via Hasura event_id) and fallback chain support. Carries direction (INBOUND, OUTBOUND), content_type (TEXT, TEMPLATE, MEDIA), rendered_content, template_id (soft FK to Backoffice NotificationTemplate for automated messages), status (QUEUED, SENT, DELIVERED, READ, FAILED), and provider-confirmed delivery/read timestamps. Replaces the former Commerce-owned CommunicationLog by centralizing all messaging records β€” both automated and agent-initiated β€” in a single audit trail. See notification-pipeline-protocol.md for the dispatch pipeline.

Trigger-Based Automation: The Communications schema does not poll domain pillars. Instead, Hasura Event Triggers fire webhooks to the Communications service when domain mutations occur (e.g., trip status β†’ "Delayed"). Hasura Scheduled Triggers handle time-based workflows (e.g., 24-hour pre-trip reminders). Commerce and Operations remain completely ignorant of messaging protocols β€” they only mutate their own state. See workflow-orchestration.md for the event trigger implementation patterns.

5. Customer Intelligence Context [future β€” Phase 3] ​

An event-sourced analytics domain enabling the 360Β° Customer Profile vision β€” behavioral aggregation, customer segmentation, and intelligent recommendations.

This context does not exist in Phase 1 or Phase 2. This section establishes its architectural intent and ensures the operational contexts emit the events it will consume. See ADR-021 for the full specification.

  • CustomerActivityLog: An event-sourced ledger recording all customer touchpoints across contexts β€” bookings, boardings, purchases, communications, cancellations, no-shows. Consumed from domain events emitted by Commerce, Operations, Communications, and Backoffice.
  • BehaviorAggregate: Materialized per-customer metrics computed from the activity log: lifetime_revenue, total_bookings, last_booking_at, preferred_seat_position, preferred_boarding_point, no_show_count, average_booking_lead_time.
  • CustomerSegment: Operator-definable cohort definitions for targeted marketing (e.g., "frequent Nordsee travelers", "high-value dormant customers", "repeat no-shows"). Supports both rule-based and ML-driven segmentation.
  • EngagementScore: Computed customer health metrics β€” NPS, satisfaction, churn risk β€” derived from communication response rates, booking recency, and feedback.
  • RecommendationModel: ML pipeline outputs for per-user suggestions ("you may like Ostsee 5-Tage") and collaborative filtering ("passengers who booked Nordsee also booked Bodensee").
  • ChannelPreference: Learned preferred communication channel per customer, fed back to Communications for optimal delivery routing.

Relationship to PassengerProfile: PassengerProfile (Backoffice) remains the identity anchor β€” name, contact info, dietary needs. Customer Intelligence reads it via PassengerProfileUpdated events but never writes to it. CI exposes a CustomerInsight read model consumed by the Workspace UI alongside the profile, providing the full 360Β° view without coupling Backoffice to analytics logic.


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.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.

Internal documentation β€” Busflow