# Domain model ## Entities | Entity | Scope | Purpose | | ------ | ----- | ------- | | **Tenant** | — | A travel agency. The owner of all tenant-scoped data. | | **User** | per-tenant | Agency staff who sign in (admin or agent). | | **Airport** | global | Reference data (IATA code, city, country, timezone). | | **Airline** | global | Reference data (IATA code, name). | | **Flight** | per-tenant | Sellable inventory: a route on a date at a fare, with seats. | | **Booking** | per-tenant | A confirmed sale against a flight. | | **Passenger** | per-tenant | A traveller on a booking. | "Per-tenant" entities carry a `tenant_id` and cascade-delete with the tenant. "Global" entities are shared by all tenants and have no tenant. ## Relationships ``` Tenant 1───* User Tenant 1───* Flight ───* Booking ───* Passenger │ │ Airline, Airport User (the agent who sold it) (global refs) ``` - A **Flight** references a global Airline and two global Airports (origin, destination), but belongs to one tenant — so the same physical route exists as a separate, independently-priced Flight row per tenant. - A **Booking** references a Flight, the User who made it, and has many Passengers. ## Key fields & rules **Tenant** — `slug` (unique), `name`, `status` ∈ {`active`, `suspended`}. A suspended tenant's users cannot sign in, and it is hidden from the public tenant list. **User** — `email`, `password_hash` (bcrypt), `full_name`, `role` ∈ {`admin`, `agent`}. Email is **unique per tenant, not globally** — the same person can hold accounts at several agencies. **Flight** — `flight_number`, `departure_time`/`arrival_time`, `cabin_class` ∈ {`economy`, `premium_economy`, `business`, `first`}, `base_price`, `currency`, `seats_total`, `seats_available`, `status` ∈ {`scheduled`, `delayed`, `cancelled`}. Constraints guarantee `seats_available ≤ seats_total` and `arrival_time > departure_time`. **Booking** — `booking_reference` (the PNR, **unique per tenant**), `passenger_count`, `total_amount`, `currency`, `status` ∈ {`confirmed`, `cancelled`}, contact email/phone. **Passenger** — name, optional date of birth, passport number, seat. ## Invariants the system must uphold - **No overselling.** A booking is created in a transaction that row-locks the flight (`SELECT … FOR UPDATE`) before reading availability, so two concurrent sales of the last seat cannot both succeed — the second gets a 409. - **Seat accounting round-trips.** Confirming a booking decrements `seats_available` by the passenger count; cancelling returns them (guarded so a double-cancel can't credit twice). - **PNR uniqueness is per tenant**, generated from an alphabet that omits easily-confused characters (no I/O/0/1). - **Tenant isolation** on every read and write (see [Overview](01-overview.md) and [SECURITY.md](../../SECURITY.md)). The authoritative schema is in [`backend/migrations/001_init.sql`](../../backend/migrations/001_init.sql).