Skip to content

Financial Model & Revenue Dashboard

FinancialModelService computes the company P&L (revenue, COGS, contribution margins, unit economics) from real bookings; the Filament Revenue Dashboard renders it for admins and finance users.

  • Answering “how much revenue have we recognized” — this service is the single definition
  • Adding a widget or report that must match the dashboard’s numbers (reuse the service, don’t re-derive)
  • Tuning the cost-rate assumptions (config/financial.php)

A booking counts as revenue-recognized when its status is one of (FinancialModelService::REVENUE_STATUSES):

pending_flight_booking, flight_booking_in_progress, flight_booking_failed, flights_confirmed, pending_land_confirmation, confirmed, awaiting_balance, fully_paid, completed

i.e. every status where payment has been processed. Pre-payment states (draft, checkout, quotation states, pending_payment, payment_processing) and terminal cancelled/expired never count. Revenue is SUM(bookings.total_amount) windowed by bookings.booked_at.

Config file: backend/config/financial.php

  • margin_percent / margin_basis — average margin used to derive COGS in the unit-economics model (margin_basis is cost markup vs sale margin; see App\Enums\MarginBasis)
  • payment_processing.{ES|UK|US} — per-market Stripe rate + fixed fee per booking
  • blended_payment_rate — fallback payment rate for markets without explicit rates and for unit economics
  • infrastructure_rate, travel_accruals_rate — variable cost rates applied to revenue

No env vars; all rates live in config.

All methods accept an optional date window (defaults: start of year → now) and an optional market code filter (joins offers → products_by_market → markets).

  • getDateRangeData() — per-market and total: revenue, booking count, COGS, payment costs, average booking value; plus CM1 = revenue − COGS, CM2 = CM1 − payment costs − infrastructure − travel accruals (with % of revenue). COGS is backed out of revenue per booking using the frozen margin/basis snapshotted on the booking (bookings.margin, bookings.margin_basis), falling back to the live offer only for legacy rows — so historical P&L is immutable even when offers are edited.
  • getMonthlyData() — bookings and revenue per month per market, for the charts.
  • getCogsBreakdown() — COGS split into international flights, domestic flights (from offer_flights.price by flight_type) and DMC/land (offers.land_base_price).
  • getUnitEconomics() — per-booking margin waterfall. With a booking_id: real data (PVP = total_amount, COGS = offer base_price or derived from margin, real flight/land costs). Without: the average booking over the same status/date/market window as the P&L, using config rates.
  • calculatePaymentCosts() — per-market revenue × rate + bookings × fixed, or the blended rate when the market has no explicit entry.

backend/app/Filament/Pages/RevenueDashboard.php — the panel’s default dashboard (Dashboard page, title “Dashboard”). Access: admins and finance users (canAccess() checks isAdmin() / isFinance()).

Header filters apply to every widget: start/end date (end date defaults to end of year), market, and a booking picker for unit economics. Widgets (backend/app/Filament/Widgets/Revenue/):

Widget Shows
RevenueKpiStatsWidget KPI cards: revenue, CM1 %, CM2 % — compared against the fiscal-year budget (BudgetComparisonService) when one exists, with achievement %/delta coloring
RevenueByMarketChart Bar chart: monthly actual revenue vs budget over the fiscal year (or the filtered window)
BookingVolumeChart Line chart: bookings per month, one series per market
CostBreakdownChart Doughnut: COGS split — international flights / domestic flights / DMC-land
PnlWaterfallWidget Table: full P&L waterfall with one column per market, totals, % of revenue, and budget/variance columns when a budget exists
UnitEconomicsWidget Table: margin waterfall for the average booking in the window, or a specific booking selected in the filters
  • Revenue recognition = payment processed; the REVENUE_STATUSES list above is the load-bearing definition shared by every widget
  • Historical P&L is immutable: COGS uses the margin snapshot frozen on the booking, never the editable offer
  • Markets with zero bookings still appear in per-market breakdowns (all active markets are listed)
  • Budget comparisons only render when a budget exists (Budget System)
  • Budget System — the FY budget the dashboard compares against
  • Offers — margin and margin-basis semantics
  • Source: backend/app/Services/Financial/FinancialModelService.php
  • Source: backend/app/Filament/Pages/RevenueDashboard.php
  • Source: backend/app/Filament/Widgets/Revenue/
  • Config: backend/config/financial.php