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:
- Event-Driven Decoupling: To prevent database deadlocks and tight coupling, contexts communicate primarily via asynchronous Domain Events (e.g.,
BookingConfirmed,IssueReported,DutyViolation). - 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_idas an integer without a hard database-level constraint to the Operations schema).
Unified Entity Relationship Diagram β
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 owningOperator. Hasura permission rules enforce row-level isolation by extractingx-hasura-tenant-idfrom the JWT and matching it againsttenant_id. The system assigns users to specific tenants via auser_tenant_assignmentstable, 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, andlegal_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 separateuserstable exists in the Backoffice schema. Platform-level role (auth.users.default_role):userorbusflow_staff. Theuser_tenant_assignments.default_role(MANAGER, DISPATCHER, DRIVER) determines tenant-level access, with additional capabilities grantable viauser_access_grantsfor 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 Nhostauth.usersfor authentication viauser_idFK (nullable β you can register crew members before the system provisions their login account). Status lifecycle:ACTIVEβINACTIVEβTERMINATED. Granular qualifications tracked viaCrewQualification. EmitsCrewMemberStatusChangedβ 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. EmitsQualificationExpiringβ Communications. The system tracks theDIGITAL_TACHOGRAPH_CARDtype but does not dispatch-block by default β it becomes a hard block only when the operator enables theTACHOGRAPHintegration (Phase 2). - CrewAbsence: Leave tracking for
CrewMember. Types: vacation, sick, rest day, training. Status lifecycle:REQUESTEDβAPPROVED/REJECTED. OnlyAPPROVEDabsences block dispatch. EmitsCrewAbsenceApprovedβ Operations. - Vehicle: Physical fleet assets with
vehicle_class(coach, minibus, van, double-decker),transmission_type,current_mileage_km, and aseat_map_layoutJSONB (the schema documents the structure). Status lifecycle:ACTIVEβIN_MAINTENANCEβDECOMMISSIONED. EmitsVehicleStatusChangedβ 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. EmitsVehicleInspectionOverdueβ 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
Allotmentcarries acurrencyfield (ISO 4217) to support multi-currency procurement as required by the FX engine, and astatuslifecycle:ACTIVE(negotiated and available) βCONSUMED(fully allocated to departures) βEXPIRED(pastend_date) /CANCELLED. Within the Kalkulations-Engine, theSupplierentity 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 theCostingSheetreferences its originatingSupplierto 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
TourOfferingsor requesting customCharterQuotesfor private groups. Distinguished byreseller_typeenum:AGENCY(commission-based sales channel) orCORPORATE(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 clonedCostingSheet, and linkedBoardingPoints. TheTourDepartureis the Backoffice staging entity that, when published, creates/updates the CommerceTourOfferingvia aTripPublishedevent. 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 viatemplate_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_pointsJSONB onTourOffering) synced viaTripPublishedevents. 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-definedlabel. Templates reference catalog items viatemplate_ancillary_assignments, which supports per-template price overrides and anincluded_by_defaulttoggle. Follows the same centralized-overridable cascade as boarding points and deposit config. Commerce receives the resolved catalog viaTripPublishedevents asavailable_ancillariesJSONB onTourOffering. 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_templatewith variable interpolation, andlocale. Validtrigger_eventvalues 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
Allotmentdata), FX risk buffers, and break-even metrics. Does not contain selling prices β those live onPriceMatrix. Carries astatuslifecycle:DRAFT(editable, under construction) βCALCULATED(engine has produced valid cost aggregates and triggered PriceMatrix generation) βLOCKED(immutable β automatically triggered when the first booking reachesDEPOSIT_PAIDagainst the linkedTourDeparture, per ADR-009). LOCKED means the system freezes costs, not prices. Edits to aLOCKEDsheet require cloning into a newDRAFTrevision. See costing-pricing-separation ADR. - PriceMatrix (Preisgestaltung): The market-driven selling price entity. Derives its baseline from a
CostingSheetbut evolves independently based on yield and channel strategies. Containsmargin_config(margin is a pricing rule, not a cost), resolved pricevariants[], andlist_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 viaPriceMatrixPublishedevent) β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 completeapplied_conditions[]audit trail. - TourTemplate: The abstract commercial product β the reusable blueprint for a tour type. Uses
pgvectorforai_embeddingto enable AI-assisted parsing of unstructured PDF itineraries into structured data. References a baselineCostingSheet. Scheduling a concrete departure creates aTourDeparture. Carries astatuslifecycle:DRAFT(incomplete, not yet publishable) βACTIVE(the user can schedule departures from this template) βARCHIVED(no new departures, existing ones unaffected). Storespricing_rules,pricing_config,capacity_rules, anddeposit_configas JSONB value objects (see below). - PricingRule, PricingConfig, CapacityRule & DepositConfig (Value Objects): Embedded as JSONB on
TourTemplate, validated at runtime via Valibot schemas (consistent with theCostingSheetvalue 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 becomeAppliedConditionoutput 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. EachTourDepartureinherits all VOs at creation, and the user can override them per departure.PricingRuleandPricingConfigare deep-copied (snapshotted) onto thePriceMatrixat 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_costsJSONB stores the calculated Leerkilometer distance and itemized toll cost segments under the keysleerkilometer_km,leerkilometer_cost, andtoll_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 aTourDeparturevia theTripPublishedevent. References the sourceTourDepartureandTourTemplatevia soft FKs. Carries denormalized display data. Pricing comes fromTourOfferingPriceβ a read-model projection of the BackofficePriceMatrix, synced asynchronously viaPriceMatrixPublishedevents. Lifecycle status:SCHEDULED,SOLD_OUT,COMPLETED,CANCELLED(Note:DRAFTandREADYstates belong natively toTourDeparturein Backoffice and are not projected to Commerce until published). - TourOfferingPrice (Read-Model): A flattened Commerce-side projection of the Backoffice
PriceMatrix. Storesprice_matrix_version_id(soft FK to backoffice.price_matrices),channel, andvariants(JSONB). Synced viaPriceMatrixPublishedevents. Checkout validates the cart's price against the authoritativeprice_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 selectedTourOffering, passenger count, seat holds, and chosen boarding point. Status lifecycle:ACTIVEβEXPIRED(TTL elapsed) orCONVERTED(successful payment). Converts to aBookingupon successful payment. Enables abandoned cart recovery: session expires after TTL (default 30 minutes) β Hasura Scheduled Trigger setsstatus = 'EXPIRED'β emitsCheckoutAbandonedevent β 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_methodused (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
typefield defines the behavioral category (INSURANCE,UPGRADE,EXTRA_LUGGAGE,EXCURSION,MEAL,BOARDING_SURCHARGE,OTHER) which governs system behavior (refund rules, reporting).BOARDING_SURCHARGEis auto-created by Commerce when a passenger selects a boarding point with a surcharge. Thelabelfield is operator-defined (the passenger-facing name). Each ancillary has an independent lifecycle (ACTIVE,CANCELLED,REFUNDED) to support partial cancellation. Carries an optionalcatalog_item_id(soft FK tobackoffice.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. Carriesseat_identifier(matching a key in the vehicleβsseat_map_layoutJSONB),hold_expires_at(aligned with checkout session TTL, default 30 minutes β see ADR-013), andstatusenum (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_statusviaqr_hashvalidation in the driver's offline app. - FinancialLedger (Nachkalkulation): The per-departure profitability ledger. The system auto-creates one
FinancialLedgerperTourOfferingwhen the first booking reachesDEPOSIT_PAID. It aggregates actual booking revenues (realized_revenue= sum ofPayment.amountwithstatus = COMPLETED) and actual operational expenses (realized_expense= sum fromExpenseSubmittedandOnboardSaleRecordeddomain events consumed from Operations). Snapshots two Soll baselines at creation:planned_costfromCostingSheet.total_net_costandplanned_revenuefrom the activePriceMatrix(Ξ£ variant prices Γ expected pax atCapacityRule.max_capacity; version tracked viaplanned_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). Carriescosting_sheet_id(soft FK) for Soll/Ist baseline andcurrency(ISO 4217). Status lifecycle:OPEN(accepting mutations from booking/expense events) βCLOSED(period-locked, the system finalizes all actuals via theclosed_attimestamp). See ADR-002. - TaxLedgerEntry: A generalized entity utilizing a
TaxStrategyTypeto calculate the correct tax burden based on ledger actuals. Relationship is 1:N β a singleFinancialLedgercan produce multipleTaxLedgerEntrieswhen a departure mixes tax strategies (e.g., oneMARGIN_SCHEME_25entry for the tour margin and oneSTANDARD_VATentry 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
TaxLedgerEntryandBookingdata without a persistentinvoice_line_itemstable. - 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) andMANUAL(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
TourDepartureconsists of multipleServiceLegs. Operations creates ServiceLegs when theTripPublishedevent arrives from Backoffice (see ADR-018). Carriestour_departure_id(soft FK to Backoffice),leg_typeenum (PICKUP,TRANSIT,TRANSFER,DROPOFF,REPOSITIONING), andactual_start/actual_endtimestamps for Soll/Ist comparison. APICKUP-typed ServiceLeg is the Operations-side representation of a BackofficeBoardingPoint(1:1) β see ADR-001. Lifecycle:SCHEDULEDβACTIVE(emitsServiceLegStarted) βCOMPLETED(emitsServiceLegCompleted) orDELAYED(emitsServiceLegDelayed) βCOMPLETED/CANCELLED(emitsServiceLegCancelled). DELAYED can recover to ACTIVE (emitsServiceLegDelayResolved). Full state machine with guards, actors, and cancellation resolution scenarios in schema-operations.md Β§ServiceLeg State Machine. - LegAssignment: The dispatching link tying a specific
ServiceLegto aVehicleand aCrewMember. Carriesstatus(CONFIRMED,RELEASED) andassigned_attimestamp 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 aDutyActivityentity with duration-based logging (start_time,end_time,duration_minutes) and asource_typediscriminator (TOUR_LEG,LINE_ROUTE,CHARTER,TACHOGRAPH_IMPORT,SELF_DECLARATION) to decouple compliance evaluation from the tour-specificServiceLegpipeline.CrewDutyLogremains for operational correlation;DutyActivitysupersedes it as the compliance data source. Phase 2 reserves thetachograph_dataJSONB 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 reservesfuel_levelfor future use. - RouteWaypoint: Planned geographical execution paths with ETA tracking. Carries
label(descriptive name, e.g., "KΓΆln Hbf") andwaypoint_typeenum (BOARDING_STOP,REST_AREA,WAYPOINT,BORDER_CROSSING).BOARDING_STOPwaypoints 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
CrewMembercreates and ties to aVehicle. Captures any observed problem (e.g., cracked mirror, engine warning light). Status lifecycle:OPEN(driver submits) βIN_PROGRESS(Backoffice schedules aVehicleInspectionand emitsVehicleInspectionScheduled) βRESOLVED(Backoffice completes inspection and emitsVehicleInspectionCompleted). Whenmaintenance_urgency = IMMEDIATE, the system emitsVehicleMaintenanceRequiredβ 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. SupportsattachmentsJSONB 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(emitsIncidentCreatedwithseverityandtypein payload) βACKNOWLEDGED(dispatcher setsassigned_to) βIN_PROGRESS(dispatcher acting) βRESOLVED(setsresolved_at, emitsIncidentResolved).IncidentCreatedis 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 carryreporter_crew_id=NULLandseverity=CRITICAL(ensures entry into the broadcast chain β dispatcher approval gate). BREAKDOWN incidents do NOT directly triggerVehicleMaintenanceRequiredβ vehicle maintenance is the responsibility ofIssueReport(see ADR-008). The Driver Hub prompts the driver to file an IssueReport alongside a BREAKDOWN incident. Carriesgeo_coordinatesfor the GPS pin location anddescriptionfor unstructured details. - OnboardSale: Point-of-Sale records managing the driver's cash box and dynamic upsells. Carries
sale_statuslifecycle:ACTIVEβVOIDED(driver or dispatcher cancels before end-of-day, emitsOnboardSaleVoided) /REFUNDED(role-gated, emitsOnboardSaleRefunded). Trackscrew_member_id(the seller),payment_method(CASH,PAYMENT_LINK,CARD_READER), andquantity. Supports digital payments viapayment_status(PAID,PENDING_LINK,FAILED) andcheckout_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 viachange_events(scope = GOBD). On sync, emitsOnboardSaleRecordedβ Commerce FinancialLedger forrealized_expenseaggregation whenpayment_status = PAID. Refund authorization is payment-method-scoped:CASHrefunds are available to any user with sufficient permissions (MANAGER, DISPATCHER, or DRIVER withcan_refund_onboard_salegrant withindriver_cash_refund_limit), supporting owner-operators who drive themselves.PAYMENT_LINK/CARD_READERrefunds 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. Carriesreceipt_image_keyfor the uploaded photo (storage key, not URL) andocr_dataJSONB for extracted fields. Includesnet_amount,vat_amount, andvat_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, emitsExpenseSubmittedβ Commerce FinancialLedger) orREJECTED(terminal state β dispatcher rejects withrejection_reason, driver creates a newExpenseReceiptrow withreplaces_receipt_idlinking to the rejected original). - BoardingEvent: Records the QR scan check-in of a passenger during boarding. References
ticket_id(soft FK to CommerceTicket) and capturescheck_in_status(SUCCESS,INVALID,ALREADY_SCANNED,MANUAL_OVERRIDE),qr_hash, and physical baggage metrics. Carriesexpected_service_leg_idwhen the system usesMANUAL_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 accurateBookingNoShowdetection perTicket, not perBooking. This entity replaces the previous ambiguous Ticket dual-ownership β Commerce owns theTicket, Operations owns theBoardingEvent.
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_eventsserver-side on receipt of synced records. The client sends raw mutations with client-generatedidempotency_keyUUIDs; the server deduplicates via thesync_idempotency_logtable. 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.
CrewDutyLogpersists the raw events. In Phase 2, a statelessComplianceEvaluatordomain service in Backoffice will consumeDutyActivityrecords (source-agnostic: tours, scheduled routes, chartering, tachograph imports) and produce per-driverComplianceStatus(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 theServiceLegβTourDeparturepipeline. 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), typedprovider_configJSONB (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 viapgsodium. 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. Carriesidentifiers[](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, aChannelAccount, and aContact. Tracks the full delivery lifecycle from dispatch through provider confirmation, with idempotency dedup (via Hasuraevent_id) and fallback chain support. Carriesdirection(INBOUND, OUTBOUND),content_type(TEXT, TEMPLATE, MEDIA),rendered_content,template_id(soft FK to BackofficeNotificationTemplatefor automated messages),status(QUEUED, SENT, DELIVERED, READ, FAILED), and provider-confirmed delivery/read timestamps. Replaces the former Commerce-ownedCommunicationLogby 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 viaPassengerProfileUpdatedevents 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:
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_VATvs.MARGIN_SCHEME_25). Output: aCostingSheetwithtotal_net_costand status =CALCULATED.Phase 2 β Price Generation: Applies
margin_configrules to the cost base, generates thelist_price, then expands into a fullPriceMatrixwithvariants[]covering all combinations of room type Γ demographic Γ season Γ early-bird tier. Each variant carries a completeapplied_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.