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.
When to Use
Section titled “When to Use”- 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)
Revenue Recognition
Section titled “Revenue Recognition”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.
Configuration
Section titled “Configuration”Config file: backend/config/financial.php
margin_percent/margin_basis— average margin used to derive COGS in the unit-economics model (margin_basisiscostmarkup vssalemargin; seeApp\Enums\MarginBasis)payment_processing.{ES|UK|US}— per-market Stripe rate + fixed fee per bookingblended_payment_rate— fallback payment rate for markets without explicit rates and for unit economicsinfrastructure_rate,travel_accruals_rate— variable cost rates applied to revenue
No env vars; all rates live in config.
Computations
Section titled “Computations”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; plusCM1 = 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 (fromoffer_flights.pricebyflight_type) and DMC/land (offers.land_base_price).getUnitEconomics()— per-booking margin waterfall. With abooking_id: real data (PVP =total_amount, COGS = offerbase_priceor 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-marketrevenue × rate + bookings × fixed, or the blended rate when the market has no explicit entry.
Revenue Dashboard
Section titled “Revenue Dashboard”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 |
Business Rules
Section titled “Business Rules”- Revenue recognition = payment processed; the
REVENUE_STATUSESlist 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)
Related
Section titled “Related”- 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