Busflow Docs

Internal documentation portal

Skip to content

Kalkulations-Engine β€” Costing & Pricing Specification ​

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 PRODUCT_domain-model.md for entities, schema-backoffice.md for schema, tax-engine.md for service placement, 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.

  • base_currency: The operator's home currency (default: EUR)
  • fx_rates[]: Array of { target_currency, rate, buffer_percentage } 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).

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 ​

mermaid
flowchart TD
    A["**Eigenleistungen** (Diesel, Fahrer, Maut, Abschreibung)"] --> C
    B["**Fremdleistungen** (Hotel, FΓ€hre, Guide β€” via Allotment)"] --> C
    C["Ξ£ Netto-Selbstkosten"]
    C --> D["+ Marge (konfigurierbar pro Kategorie)"]
    D --> E["= Netto-Verkaufspreis"]
    E --> F{"Steuer-Strategie?"}
    F -- "Nur Eigenleistungen" --> G["Regel-USt (19% auf Netto-VK)"]
    F -- "Fremdleistungen enthalten" --> H["Β§ 25 UStG Margensteuer\n(USt nur auf Marge)"]
    G --> I["**Brutto-Verkaufspreis**"]
    H --> I
    I --> J["Γ· PricingRules\n(Erwachsene / Kinder / Senioren)"]
    J --> K["Endkundenpreis pro Kategorie"]

    C -.-> L{"DB-PrΓΌfung"}
    L -- "Break-even Pax erreicht?" --> M["βœ… Abfahrt wirtschaftlich tragfΓ€hig"]
    L -- "Unter Break-even" --> N["⚠️ Warnung an Disponenten"]

    style A fill:#1e3a5f,color:#fff
    style B fill:#1e3a5f,color:#fff
    style C fill:#2d5a3d,color:#fff
    style I fill:#5a1e3a,color:#fff
    style K fill:#5a1e3a,color:#fff

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