Busflow Docs

Internal documentation portal

Skip to content

Feature Spec: Ancillary Catalog & Extras (Backoffice) ​

Goal: Define how operators create and manage bookable extras (insurance, seat upgrades, luggage, excursions, meals) on tour templates, how these flow to the booking widget, and how they appear in the workspace.


Domain Concepts ​

Centralized, Overridable Design ​

Operators define extras once at the operator level (the "Catalog"), then attach them to templates. Per-template and per-departure overrides follow the same cascade pattern as boarding points and deposit config.

Convention over Configuration ​

Ancillary types are behavioral categories, not passenger-facing product categories. Operators pick a type and provide a label β€” they never define behavioral categories. This allows the system to determine refund rules, reporting, fulfillment hints, and invoice line grouping automatically.

The V0.1 taxonomy is intentionally small:

TypeUse ForBehavior It Unlocks
INSURANCETravel cancellation or assistance productsPotential third-party claim flow; usually excluded from simple partial-refund assumptions
SEAT_UPGRADEFront-row seats, panoramic seats, comfort seatsConnects to seat reservation/assignment behavior
LUGGAGEExtra bags, bikes, bulky itemsQuantity limits and operational handling notes
EXCURSIONOptional day trips, museum entries, guide programsCapacity/vendor reporting and cancellation cutoffs
MEALPre-booked meals, packed lunches, dietary-relevant packagesDietary/preparation reporting
OTHEROperator-specific extras without special behaviorNeutral fallback; should stay rare

Room categories and single-room supplements are not ancillaries by default. They belong in PriceMatrix variants / PricingConfig.room_surcharge, because they change the base commercial product rather than behaving like an add-on.


Data Model ​

Tier 1: Operator-Level Catalog ​

A global library of extras that the operator maintains. Reusable across all templates.

backoffice.ancillary_catalog_items ​

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique catalog item
tenant_idUUIDFK (operators.id), Not NullOwning tenant
typeVARCHARNot NullBehavioral category: INSURANCE, SEAT_UPGRADE, LUGGAGE, EXCURSION, MEAL, OTHER.
labelVARCHARNot NullOperator-defined display name shown to passengers
descriptionTEXTNullableOptional longer description for the booking widget product page
cover_image_keyVARCHARNullableOptional image key for displaying the item in the booking widget.
default_priceDECIMALNot NullDefault unit price in the operator's currency
currencyVARCHARNot Null, Default: EURISO 4217
is_per_passengerBOOLEANNot Null, Default: truetrue: price applies per passenger. false: price applies per booking.
max_quantityINTNullableMaximum units per booking. Null = unlimited.
tax_strategy_overrideVARCHARNullableSTANDARD_VAT or MARGIN_SCHEME_25. Null = inherit from the parent tour's tax strategy.
statusVARCHARNot Null, Default: ACTIVEACTIVE or ARCHIVED. Archived items remain on existing bookings but do not appear in new checkouts.
sort_orderINTNot Null, Default: 0Display order in the booking widget and template configuration

NOTE

Unique constraint: (tenant_id, label) β€” prevents duplicate names within a tenant.

Tier 2: Template-Level Assignment ​

Which catalog items are offered on which tour template, with optional price overrides. (Note: Bundling of ancillaries is explicitly deferred to [future]).

backoffice.template_ancillary_assignments ​

ColumnTypeConstraintsDescription
idUUIDPrimary KeyUnique assignment
tenant_idUUIDFK (operators.id), Not NullOwning tenant
tour_template_idUUIDFK (tour_templates.id), Not NullTarget template
ancillary_catalog_item_idUUIDFK (ancillary_catalog_items.id), Not NullCatalog item being assigned
price_overrideDECIMALNullablePer-template price override. Null = use default_price from catalog.
included_by_defaultBOOLEANNot Null, Default: falsetrue: auto-added to every booking (e.g., travel insurance on premium tours). Passenger can opt out.
enabledBOOLEANNot Null, Default: trueQuick toggle to disable an extra for this template without removing the assignment.

Tier 3: Departure-Level Override (Inherited) ​

Departures inherit their template's ancillary assignments.

  • Default behavior: A departure inherits all assignments from its template. No separate table needed at [v0.1].
  • [v0.2]: Add a departure_ancillary_overrides table for per-departure price changes or disabling specific items.

Resolution Cascade ​

Effective Price = departure_override ?? template_override ?? catalog_default_price
Effective Enabled = departure_enabled ?? template_enabled ?? true (if assigned)

Commerce Projection ​

When a TourDeparture publishes, the payload includes the resolved ancillary catalog for that departure:

typescript
interface TourDeparturePublishedAncillary {
  catalog_item_id: string;
  type: AncillaryType;
  label: string;
  description: string | null;
  cover_image_key: string | null;
  price: number;
  currency: string;
  is_per_passenger: boolean;
  max_quantity: number | null;
  included_by_default: boolean;
  sort_order: number;
}

Commerce stores this as a denormalized JSONB array on tour_offerings.available_ancillaries.

IMPORTANT

New column on commerce.ancillaries: Add catalog_item_id UUID (nullable, soft FK to backoffice.ancillary_catalog_items). Null for manually added ancillaries. Enables Backoffice β†’ Commerce traceability.


Workspace UX ​

Tour Template Detail β€” Tab 5: Settings ​

Ancillaries live in the Settings tab under an "Extras & Add-ons" section.

Layout: A card-list showing all ancillary catalog items assigned to this template. Actions:

  • "Add Extra" β†’ Picker showing all ACTIVE catalog items not yet assigned to this template. Multi-select enabled.
  • "Edit Price" β†’ Inline edit the price_override.
  • "Manage Catalog" β†’ Navigates to /settings/catalog.
  • Remove (trash icon) β†’ Removes the assignment.

Global Ancillary Catalog Management ​

Path: /settings/catalogLayout: Table of all ancillary_catalog_items for the tenant.

Smart Defaults (Concierge Onboarding): The tenant provisioning flow seeds 3 starter items (e.g., ReiserΓΌcktrittsversicherung, ZusatzgepΓ€ck, Sitzplatzreservierung vorne). Operators customize labels and prices immediately β€” they never face an empty catalog.


Commerce UX: Passenger Side (Booking Widget) ​

During checkout, after the passenger selects their boarding point and ticket type:

Step: "Extras & Add-ons"

  • Cards for each available ancillary, sorted by sort_order.
  • Items with included_by_default = true appear pre-selected with an "Included" badge. The passenger can remove them.
  • Each card shows: label, cover image (if present), price, description.
  • Toggle to add/remove. Quantity selector if max_quantity > 1.
  • Note: Availability of specific ancillaries based on the chosen boarding point is explicitly deferred.

V0.1 Pricing Tab Clarification ​

The V0.1 pricing model uses Centralized Pricing Categories + Base Price + Overrides.

Operator-Level: Pricing Categories ​

Path: /settings/pricing Operators define reusable pricing categories (e.g., ADULT, CHILD, INFANT) with discount_type and discount_value. Exactly one category is marked as base (is_base = true).

Template-Level: Base Price + Category Overrides ​

  1. Base Price β€” A single number field: "Adult price per person".
  2. Category List β€” Shows all operator-level pricing categories with the computed price based on the category discount.
  3. Per-template overrides β€” The operator can override the discount for a specific category on this template.

(No CostingSheet, no margin calculator, no multi-channel pricing at V0.1).


Phasing ​

FeatureHorizon
Operator-level ancillary catalog (CRUD)[v0.1]
Template β†’ ancillary assignment with price overrides[v0.1]
Commerce projection via TourDeparturePublished[v0.1]
Booking widget: extras selection step[v0.1]
Concierge onboarding seeds (3 starter items)[v0.1]
Operator-level pricing categories (CRUD)[v0.1]
Template base price + category overrides[v0.1]
Ancillary cover image support[v0.1]
Departure-level ancillary overrides[v0.2]
Per-departure pricing overrides[v0.2]
B2B reseller pricing catalogs[v0.2]
Ancillary analytics[v0.2]
Filter ancillaries by Boarding Point[future]
Ancillary Bundling[future]

Internal documentation β€” Busflow