Busflow Docs

Internal documentation portal

Skip to content
Reviewed 02 May 2026

Kalkulations-Engine ​

Architectural decision: We separate Costing and Pricing into two distinct entities per the costing-pricing-separation ADR. The CostingSheet calculates costs; the PriceMatrix manages selling prices.

Domain context: Backoffice Bounded Context. See domain-backoffice.md for entities, schema-backoffice.md for schema, tax-engine-protocol.md for tax strategy resolution, and pricing-screen.md for UI.


Domain Service: CalculationEngine ​

The CalculationEngine is a central Domain Service residing in the Bounded Context "Backoffice" (Tour Planning & Inventory). It operates in two sequential phases:

  1. Cost Calculation β†’ populates the CostingSheet with aggregated costs
  2. Price Generation β†’ creates a PriceMatrix entity by applying margin rules and tax strategy to the cost base
  • Orchestration: Gathers procurement data from the Inventory context (Supplier, Allotment), fleet operating costs from the Vehicle/Fleet context (km rates, tolls, driver per diems).
  • Input: Accepts a CostingSheet (either TEMPLATE_BASELINE, DEPARTURE_CLONE, or CHARTER_CUSTOM).
  • Output Phase 1: Produces a fully costed CostingSheet with total_net_cost and status β†’ CALCULATED.
  • Output Phase 2: Creates a PriceMatrix (DRAFT) by applying margin_config + PricingRule + tax strategy to the cost base. Emits PriceMatrixGenerated domain event.
  • Execution Modes:
    • Asynchronous (Catalog): For the classic KataloggeschΓ€ft, the engine runs as a batch process β€” often months before season start β€” pre-calculating all selling prices for downstream systems.
    • Synchronous (B2B & Charter): For Anmietverkehr, the same engine operates in real-time, computing prices on-the-fly.

Phase 1: Cost Calculation (CostingSheet) ​

Every bookable product passes through the same deterministic pipeline to calculate costs. This phase produces the CostingSheet β€” the operator's factual cost base.

1. Cost Aggregation (Selbstkosten-Ermittlung) The engine collects all cost components that the operator incurs to produce the trip:

  • Own operating costs (Eigenleistungen): Diesel per projected km, driver daily rates, vehicle depreciation, insurance, parking fees, tolls (the routing service supplies these).
  • Third-party procurement costs (Fremdleistungen): Net purchase prices for hotels, ferries, local guides, entrance tickets β€” each linking to a specific Allotment from a contracted Supplier. The system stores these as individual procurement_items on the CostingSheet.

The sum of all Eigen- and Fremdleistungen yields the Netto-Selbstkosten (total net cost of production) for the trip.

1b. Currency & FX Risk Calculation (WΓ€hrungs- und Risikopuffer) For destinations involving foreign currencies (e.g., CZK for Czech Republic, GBP for Great Britain), the engine automatically applies stored exchange rates. It also uses a configurable risk buffer to hedge against currency fluctuations between calculation time and trip execution. See ADR-035 for the phased FX strategy.

  • base_currency: The operator's home currency (default: EUR)
  • fx_rates[]: Array of { target_currency, rate, buffer_percentage, source, captured_at } entries
  • The engine applies the buffer multiplicatively on top of the spot rate (e.g., a 3% buffer on GBP protects the operator against moderate GBP appreciation).
  • [planned β€” Phase 2b]: The system pre-fills rates from ECB daily reference data (fx_reference_rates table) when the operator adds a foreign-currency procurement item. The operator can override with a manually negotiated bank rate. A staleness badge displays the rate age on the Pricing Screen.
  • [planned β€” Phase 3]: At PriceMatrix publication, the engine compares snapshotted FX rates against current ECB data. If any rate drifts beyond a configurable threshold (default: Β±2%), the system displays a confirmation dialog.

2. Contribution Margin Validation (Deckungsbeitrags-PrΓΌfung) Before the engine derives prices, it validates the cost base against profitability targets:

  • Break-even point (break_even_pax): The minimum number of paying passengers required to cover all costs.
  • Planned contribution margin (planned_contribution_margin): The absolute € amount the operator expects to earn above cost. The engine flags any configuration where the target margin is mathematically unreachable at the given capacity.

This turns the CostingSheet from a simple calculator into an active planning instrument β€” the operator sees immediately whether a departure is viable.

3. Tax Strategy Resolution (Steuer-Engine) The tax_strategy field on the CostingSheet determines which taxation regime applies:

  • STANDARD_VAT: Regular German VAT (currently 19%) applied to selling prices. Applicable when the operator provides purely own services (Eigenleistungen).
  • MARGIN_SCHEME_25 (Β§ 25 UStG β€” Margenbesteuerung): Tax calculated only on the margin (difference between gross customer price and gross procurement costs). Mandatory for most DACH package tours bundling third-party services (Reisevorleistungen).

The tax engine dynamically selects the correct strategy based on the composition of the CostingSheet. If all line items qualify as Eigenleistungen β†’ STANDARD_VAT. If any Fremdleistung is present β†’ MARGIN_SCHEME_25.

Output: After Phase 1, the CostingSheet holds total_net_cost and status = CALCULATED. No selling prices exist yet.


Phase 2: Price Generation (PriceMatrix) ​

The Pricing Engine takes the CostingSheet cost base and produces a PriceMatrix β€” the operator's market-driven selling price strategy.

4. Margin Application (Margenaufschlag) The operator configures margin rules per cost category. The system supports both percentage-based and absolute markups, applied granularly:

  • A flat percentage on all third-party hotel costs (e.g., +18%)
  • A different margin on ferry bookings (e.g., +12%)
  • A fixed markup per seat on own transport costs (e.g., +€15/pax)

These rules reside in margin_config on the PriceMatrix (margin is a pricing rule, not a cost). The system inherits them from the TourTemplate baseline, but the user can override them per channel.

5. List Price Generation (Brutto-VK-Ermittlung) The engine computes the customer-facing base gross price and stores it as list_price on the PriceMatrix. This represents the List Price β€” the default adult, base-accommodation rate before any demographic, seasonal, or promotional adjustments. It serves as the operator's headline catalog price.

6. Price Matrix Generation (Preismatrix-Erzeugung) Starting from the anchor price, the engine generates a complete price matrix by applying conditions in fixed priority order (Demographic β†’ Room β†’ Season β†’ Early-bird β†’ Tax). See Β§Composition Algorithm below for the full specification. The engine stores the resulting matrix as variants[] (JSONB) on the PriceMatrix entity. Each PriceMatrix starts as DRAFT and requires explicit publication by the operator.

Yield Management: After initial generation, the operator can directly edit the PriceMatrix to adjust selling prices β€” no CostingSheet modification needed. Manual overrides (Level 4: "Dispatcher's Veto") take absolute priority. [planned β€” Phase 2]: Channel Rules (Level 2) and Algorithmic Yield Rules (Level 3) for automated time/occupancy-based price adjustments.


Calculation Flow Diagram ​

100%

CostingSheet β€” Internal Data Structure (Pure Costs) ​

The CostingSheet entity lives in schema: backoffice and uses PostgreSQL JSONB columns for maximum flexibility across diverse tour configurations. This entity contains only costs β€” selling prices live on the PriceMatrix entity.

FieldTypeDescription
idUUIDPrimary key
operator_idUUID (FK)Owning tenant
source_typeEnumTEMPLATE_BASELINE, DEPARTURE_CLONE, or CHARTER_CUSTOM β€” indicates whether this sheet is a reusable template budget, a date-specific clone, or a one-off charter calculation
statusEnumDRAFT, CALCULATED, or LOCKED β€” lifecycle gate preventing cost edits after bookings exist. LOCKED = costs frozen, not prices.
versionIntegerOptimistic locking counter for concurrent access
parent_sheet_idUUID (FK)For DEPARTURE_CLONE: references the TEMPLATE_BASELINE from which the system cloned this sheet. Tracks revision lineage.
calculated_atTimestampTimestamp of last successful cost calculation. Null if status = DRAFT.
fixed_costsJSONBProjected fixed costs: driver daily rates, vehicle depreciation, insurance premiums, parking
variable_costsJSONBDistance-dependent costs: diesel per km, projected toll fees (sourced from services/routing)
procurement_items[]JSONB[]Array of typed CostComponent value objects (see Embedded Value Objects below). Each entry: { supplier_id, allotment_id, description, net_unit_cost, quantity, currency, category, service_type, geography, tax_rule }
planned_contribution_marginDecimalPart of the ContributionMargin value object β€” target contribution margin in € for the entire departure
break_even_paxIntegerPart of the ContributionMargin value object β€” calculated minimum passenger count to cover all costs
tax_strategyEnumSTANDARD_VAT or MARGIN_SCHEME_25 β€” auto-determined by presence of Fremdleistungen (via CostComponent.service_type), manually overridable
fx_configJSONBTyped CurrencyProfile value objects: { base_currency, fx_rates: [{ target_currency, rate, buffer_percentage }] } β€” applies when the system uses procurement items in non-EUR currencies
total_net_costDecimalComputed: Ξ£ fixed + variable + procurement (incl. FX-adjusted amounts)
currencyString (ISO 4217)Calculation currency (default: EUR)
created_at / updated_atTimestampAudit trail

PriceMatrix β€” Internal Data Structure (Selling Prices) ​

The PriceMatrix entity lives in schema: backoffice as a first-class entity separate from CostingSheet. It manages selling prices and the margin rules that derive them. See costing-pricing-separation ADR.

FieldTypeDescription
idUUIDPrimary key
tenant_idUUID (FK)Owning tenant
costing_sheet_idUUID (FK)Source cost calculation this price derives from
tour_departure_idUUID (FK)Nullable β€” null for template-level previews
channelStringSales channel this matrix serves (default: DEFAULT). Supports 1:N per CostingSheet.
statusEnumDRAFT, PUBLISHED, or ARCHIVED
versionIntegerMonotonic version counter (immutable append β€” each edit creates a new version)
margin_configJSONBArray of typed MarginTarget value objects β€” margin rules applied to derive selling prices from costs
pricing_rules_snapshotJSONBDeep copy of PricingRule[] active at generation time. Immutable β€” enables historical audit of discount rules even after TourTemplate changes.
pricing_config_snapshotJSONBDeep copy of PricingConfig (room surcharge, accommodation flag, season tiers, early-bird tiers) at generation time. Immutable.
variantsJSONBTyped PriceVariant[] β€” resolved price variants with variable_cost_snapshot, applied_conditions[], and tax fields. See full schema below.
list_priceDecimalThe List Price β€” base adult, base-accommodation, default-season, no-early-bird gross selling price. All variants derive from this anchor.
currencyString (ISO 4217)Selling price currency (default: EUR)
fx_config_snapshotJSONB[planned β€” Phase 2] Deep copy of CostingSheet.fx_config at generation time. Immutable. Empty {} for EUR-only tours. See ADR-035.
generated_atTimestampWhen initially derived from CostingSheet
published_atTimestampWhen made available to Commerce (null if still DRAFT)
superseded_byUUID (FK)Points to the next version in the immutable chain (null = current)
created_atTimestampAudit trail

PriceMatrix β€” Variant Schema, Composition Algorithm & Edge States ​

Full specification: See price-matrix-variant-spec.md for the complete PriceVariant TypeScript interfaces, PricingConfig input VOs, the 5-step composition algorithm, edge state table, and embedded value object catalog (Kalkulations-Bausteine).


Internal documentation β€” Busflow