# Charter Order Management System — CLAUDE.md

## Project Overview

Internal operations platform for a charter bus brokerage company, deployed at **app.managedcharter.com**. The company sells charter bus services to clients (B2B and B2C) and fulfills orders through a network of supplier bus companies across Europe. One client order can contain 1–100+ individual trips spanning months, executed by different suppliers, with constant changes flowing from clients to suppliers.

**Core problem**: managing dozens of concurrent trips per order without losing data when clients request changes that must be relayed to suppliers, tracked, and confirmed.

**Platform scope**: ManagedCharter is the operations layer — bookings, quotes, confirmations, invoicing, customer/supplier management, dispatching, payments, reporting. Communication (email, messaging) stays in Missive — ManagedCharter provides "Copy email text" and deep-link hooks for Missive integration but does NOT replicate messaging.

**Three access layers**:
1. **Internal app** — full management interface for BCS staff (managers, dispatchers, finance).
2. **Customer portal** — clients log in to view their bookings, confirmations, invoices, payment status. They NEVER see supplier costs, margins, or internal notes.
3. **Supplier portal** — suppliers log in to view assigned trips, confirm assignments, enter driver/vehicle details, upload their invoices. They NEVER see client sale prices, margins, other suppliers, or internal notes.

**Tech stack**: Next.js 14+ (App Router), TypeScript, PostgreSQL, Prisma ORM, Tailwind CSS, shadcn/ui components. Auth via NextAuth.js — credential provider for internal users, separate auth flows for customer/supplier portal users. Stripe for payment processing. Deploy target: VPS with Docker.

**Architecture**: monolithic Next.js app with API routes. No microservices. Server components where possible, client components for interactive parts. All dates/times stored in UTC, displayed in user's timezone. Role-based access control enforced at API level — every endpoint checks user role and data ownership before returning data.

---

## Entity Model & Database Schema

### Core Reference Entities

#### Client
The company or individual ordering charter services.
```
Client {
  id              UUID PK
  company_name    String
  type            Enum: company | individual
  country         String?          // ISO country code
  tax_id          String?          // VAT number
  billing_address Text?
  currency        String default "EUR"  // preferred currency
  payment_terms   Int default 30   // days
  default_language String default "en"  // en, de, nl, fr, es...
  account_manager_id UUID? FK → User  // assigned sales/account manager
  annual_volume   Decimal?         // estimated annual booking volume for prioritization
  portal_enabled  Boolean default false  // whether client can access customer portal
  notes           Text?
  is_active       Boolean default true
  created_at      DateTime
  updated_at      DateTime
}
```

#### ClientContact
Multiple contacts per client. One client may have a booker, a finance person, and on-ground group leaders.
```
ClientContact {
  id          UUID PK
  client_id   UUID FK → Client
  name        String
  email       String?
  phone       String?
  role        String?          // "Booker", "Finance", "Operations", "Group Leader"
  is_primary  Boolean default false
  portal_access Boolean default false  // can this contact log into customer portal?
  portal_password String?              // hashed, only if portal_access=true
  notes       String?
  created_at  DateTime
}
```

#### Supplier
Bus company that fulfills trips.
```
Supplier {
  id              UUID PK
  company_name    String
  countries       String[]        // ["NL", "DE", "BE"] — regions they cover
  currency        String default "EUR"
  payment_terms   Int default 30
  reliability_rating  Int?        // 1-5, internal rating
  portal_enabled  Boolean default false  // can supplier access supplier portal?
  notes           Text?
  is_active       Boolean default true
  created_at      DateTime
  updated_at      DateTime
}
```

#### SupplierContact
```
SupplierContact {
  id          UUID PK
  supplier_id UUID FK → Supplier
  name        String
  email       String?
  phone       String?
  role        String?          // "Dispatcher", "Finance", "Owner"
  is_primary  Boolean default false
  portal_access Boolean default false  // can this contact log into supplier portal?
  portal_password String?              // hashed
  notes       String?
  created_at  DateTime
}
```

#### Vehicle
Specific bus in a supplier's fleet.
```
Vehicle {
  id          UUID PK
  supplier_id UUID FK → Supplier
  type        Enum: minibus | coach | double_decker | sprinter | van
  capacity    Int              // max passengers
  plate_number String?
  brand_model  String?         // "Mercedes Tourismo", "Setra S516"
  year        Int?
  class       Enum: economy | business | vip
  amenities   Json default {}  // { wifi: true, wc: true, ac: true, usb: true, kitchen: false, wheelchair: false }
  is_active   Boolean default true
  notes       String?
  created_at  DateTime
}
```

#### Driver
```
Driver {
  id          UUID PK
  supplier_id UUID FK → Supplier
  name        String
  phone       String?
  languages   String[]         // ["en", "de", "nl"]
  license_categories String[]  // ["D", "D1"]
  notes       String?
  is_active   Boolean default true
  created_at  DateTime
}
```

#### User (Internal Staff)
Internal BCS employees using the system. Role determines access.
```
User {
  id          UUID PK
  name        String
  email       String UNIQUE
  password    String           // hashed
  role        Enum: admin | sales | operations | dispatcher | finance
  is_active   Boolean default true
  created_at  DateTime
}
```
**Role permissions**:
- `admin` — full access to all modules, user management, system settings.
- `sales` — clients, quotes, orders, bookings, invoices. Cannot access supplier costs or margins.
- `operations` — orders, trips, suppliers, driver/vehicle details, dispatching. Full operational access.
- `dispatcher` — dispatching dashboard (Mission Control), operational statuses, issues, live trip details. Read-only on financials.
- `finance` — invoices, payments (client + supplier), reports. Read-only on operational details.

---

### Quote, Order & Trip (Core Operational Entities)

#### Quote
A price proposal to a client before commitment. Can convert to an Order.
```
Quote {
  id              UUID PK
  quote_number    String UNIQUE    // auto-generated: "QTE-2026-0123"
  client_id       UUID FK → Client
  client_contact_id UUID? FK → ClientContact
  manager_id      UUID FK → User
  title           String           // "Summer 2026 Program — 30 transfers"
  description     Text?
  currency        String default "EUR"
  total_amount    Decimal?         // total quoted price
  valid_until     Date             // quote expiry date
  status          Enum: draft | sent | accepted | rejected | expired | converted
  converted_to_order_id UUID? FK → Order  // set when converted
  pdf_path        String?
  notes           Text?
  created_at      DateTime
  updated_at      DateTime
}
```
**Business rules**:
- Quote can contain QuoteLineItems (trip descriptions with prices) for structured quotes, or just a total_amount for simple quotes.
- When client accepts: status → accepted, then manager creates Order from Quote. `converted_to_order_id` is set. Status → converted.
- Auto-expire: if `valid_until < today` and status = sent → status = expired (cron job or on-read check).
- Quote PDF generated from template with company branding.

#### QuoteLineItem
```
QuoteLineItem {
  id          UUID PK
  quote_id    UUID FK → Quote
  description String           // "Airport transfer CDG → Paris, 45 pax, coach"
  quantity    Int default 1
  unit_price  Decimal
  amount      Decimal          // quantity × unit_price
  sort_order  Int default 0
}
```

#### Order
Top-level contract with a client. Groups related trips together.
```
Order {
  id                UUID PK
  order_number      String UNIQUE    // auto-generated: "ORD-2026-0042"
  quote_id          UUID? FK → Quote // if created from a quote
  client_id         UUID FK → Client
  manager_id        UUID FK → User
  client_reference  String?          // client's own PO number if they have one
  status            Enum: draft | confirmed | active | completed | archived | cancelled
  currency          String default "EUR"
  payment_terms     Int?             // override client default if needed
  valid_from        Date?
  valid_until       Date?
  special_conditions Text?
  notes             Text?
  created_at        DateTime
  updated_at        DateTime
}
```
**Business rules**:
- Order status transitions: draft → confirmed (client confirmed) → active (first trip started) → completed (all trips done) → archived. Can go to cancelled from any status except completed/archived.
- `order_number` auto-generated on creation, sequential per year.
- Financial totals (total_sale, total_purchase, total_margin) are COMPUTED from child trips, never stored.

#### Trip
The atomic unit of work. A specific journey on a specific date.
```
Trip {
  id                UUID PK
  order_id          UUID FK → Order
  trip_number       Int              // sequential within order, auto-incremented
  type              Enum: transfer | round_trip | multi_stop | disposal | shuttle
  status            Enum: draft | confirmed | supplier_assigned | vehicle_assigned | ready_for_service | dispatched | in_progress | completed | cancelled | on_hold
  date_start        Date
  time_start        Time
  date_end          Date?
  time_end          Time?
  duration_hours    Decimal?
  pax_count         Int
  description       String?          // short summary: "CDG Airport → Hotel Marriott, 45 pax"
  flight_details    String?          // "AF1234, arriving 14:30 Terminal 2E" — for airport transfers
  meeting_sign_text String?          // text on the sign driver holds: "ACME Corp / Mr. Smith"
  luggage_info      String?          // "45 standard suitcases + 3 oversized"
  special_instructions Text?         // client-specific instructions visible to supplier/driver
  has_pending_changes Boolean default false  // denormalized flag for quick filtering
  has_issue         Boolean default false    // flagged by dispatcher, needs attention
  issue_description String?                  // what's the issue: "Bus delayed 30min", "Wrong vehicle sent"
  supplier_id       UUID? FK → Supplier     // primary supplier, nullable until assigned
  created_at        DateTime
  updated_at        DateTime
}
```
**Business rules**:
- `trip_number` auto-incremented within order scope (1, 2, 3...).
- `has_pending_changes` is set to true when a ChangeRequest with status pending/sent_to_supplier exists; set to false when all ChangeRequests are resolved.
- Status transitions: draft → confirmed → supplier_assigned → vehicle_assigned → ready_for_service (all details filled: vehicle, driver, contacts) → dispatched (day of service) → in_progress → completed. Can go to cancelled or on_hold from any active status. From on_hold can return to previous status.
- `ready_for_service` requires: at least one TripVehicle, at least one TripCrew, all TripStops have times.
- `has_issue` flag is set by dispatchers in Mission Control. When set, `issue_description` is required. Cleared manually when resolved.
- `flight_details` is shown to supplier and driver. Used in dispatcher view for delay tracking.
- `supplier_id` on Trip is a denormalized convenience field pointing to the primary supplier. The canonical assignment is through TripVehicle/TripCrew which reference Vehicle/Driver that belong to a Supplier.
- Trip `description` should be auto-generated from TripStops if not manually set: "{first_stop.location_name} → {last_stop.location_name}, {pax_count} pax".

---

### Trip Child Entities

#### TripStop
Ordered waypoints of the journey. Replaces fixed pickup/destination fields for full flexibility.
```
TripStop {
  id                UUID PK
  trip_id           UUID FK → Trip
  sequence          Int              // 1, 2, 3... display/route order
  type              Enum: pickup | waypoint | dropoff
  location_name     String           // "Charles de Gaulle Airport Terminal 2E"
  address           String?
  lat               Decimal?
  lng               Decimal?
  planned_arrival   DateTime?
  planned_departure DateTime?
  actual_arrival    DateTime?
  actual_departure  DateTime?
  waiting_minutes   Int?             // planned waiting time at this stop
  notes             String?          // "Enter from Parking B2, gate 7"
  created_at        DateTime
}
```
**Business rules**:
- Minimum 1 stop for disposal type, minimum 2 stops (pickup + dropoff) for transfer.
- Sequence must be unique within a trip, no gaps. Reorder = renumber all.
- First stop with type=pickup defines the trip start location. Last stop with type=dropoff defines the destination.

#### TripVehicle
Assignment of vehicles to a trip. Many-to-many with role.
```
TripVehicle {
  id          UUID PK
  trip_id     UUID FK → Trip
  vehicle_id  UUID FK → Vehicle
  role        Enum: primary | support | luggage | backup
  notes       String?
  created_at  DateTime
}
```
**Business rules**:
- At least one vehicle with role=primary should exist before trip status can become vehicle_assigned.
- When vehicle is assigned, check pax_count vs sum of primary+support vehicles' capacity. Warn (don't block) if capacity < pax_count.

#### TripCrew
Assignment of drivers to a trip with shift management.
```
TripCrew {
  id              UUID PK
  trip_id         UUID FK → Trip
  driver_id       UUID FK → Driver
  role            Enum: primary | relief
  shift_order     Int              // 1, 2, 3
  planned_start   DateTime?
  planned_end     DateTime?
  actual_start    DateTime?
  actual_end      DateTime?
  notes           String?
  created_at      DateTime
}
```
**Business rules**:
- EU driving regulations: max 9h driving/day, 45min break after 4.5h. System should WARN (not block) if a single driver is assigned to a trip with duration > 9h without a relief driver.
- Check for scheduling conflicts: same driver assigned to overlapping trips = warning.
- `shift_order` 1 = first driver, takes over at start. Primary with shift_order=1 is the lead driver (meets the group).

#### TripContact
People associated with a specific trip (group leaders, guides, etc).
```
TripContact {
  id          UUID PK
  trip_id     UUID FK → Trip
  name        String
  phone       String?
  email       String?
  role        Enum: group_leader | client_rep | guide | translator | emergency | other
  notes       String?
  created_at  DateTime
}
```

#### TripCostItem
Granular financial line items for each trip. Both purchase (cost) and sale (revenue) sides.
```
TripCostItem {
  id          UUID PK
  trip_id     UUID FK → Trip
  direction   Enum: purchase | sale
  category    Enum: base_fare | waiting_time | toll | parking | driver_accommodation | night_surcharge | airport_fee | extras | discount | cancellation_fee | other
  description String?          // "Parking Schiphol 3h", "Weekend surcharge"
  amount      Decimal          // positive for costs/revenue, negative for discounts
  currency    String default "EUR"
  supplier_id UUID? FK → Supplier  // only for purchase items
  taxable     Boolean default true
  tax_rate    Decimal default 0    // e.g. 21 for 21%
  tax_amount  Decimal default 0
  created_at  DateTime
}
```
**Business rules**:
- `sale_total` for a trip = SUM(amount + tax_amount) WHERE direction = sale
- `purchase_total` for a trip = SUM(amount + tax_amount) WHERE direction = purchase
- `margin` = sale_total - purchase_total
- At order level: aggregate all trips' totals.
- `discount` category items should have negative `amount`.
- Every trip should have at minimum one `base_fare` line for both purchase and sale.

#### TripNote
Typed notes with strict visibility separation.
```
TripNote {
  id          UUID PK
  trip_id     UUID FK → Trip
  type        Enum: internal | for_supplier | for_driver | for_client | from_supplier
  content     Text
  created_by  UUID FK → User
  created_at  DateTime
  updated_at  DateTime
}
```
**Business rules**:
- `internal` notes are NEVER exposed in any supplier-facing or client-facing exports/reports.
- `for_supplier` notes are included when generating supplier confirmations/vouchers.
- `for_driver` notes are included in driver briefing sheets.
- `for_client` notes are included in client-facing trip confirmations.
- `from_supplier` notes are created via Supplier Portal by supplier contacts. Visible to internal team only (not to client).

---

### Change Management

#### ChangeRequest
Tracks client-initiated changes to trips. This is the critical data-integrity entity.
```
ChangeRequest {
  id                      UUID PK
  trip_id                 UUID FK → Trip
  requested_at            DateTime
  requested_by            UUID FK → User   // who logged the change
  source                  Enum: client_email | client_phone | client_portal | internal
  description             Text                // "Client wants pickup 2 hours earlier and added 5 pax"
  affected_fields         Json                // ["time_start", "pax_count", "TripStop.1.planned_departure"]
  old_values              Json                // snapshot of previous values
  new_values              Json                // what it should become
  price_impact_purchase   Decimal default 0   // +150.00 means purchase goes up by 150
  price_impact_sale       Decimal default 0
  status                  Enum: pending | sent_to_supplier | supplier_confirmed | supplier_rejected | applied | cancelled
  supplier_notified_at    DateTime?
  supplier_response_at    DateTime?
  supplier_response_notes Text?
  applied_at              DateTime?
  applied_by              UUID? FK → User
  notes                   Text?
  created_at              DateTime
  updated_at              DateTime
}
```
**Business rules — CRITICAL**:
1. When a ChangeRequest is created, `trip.has_pending_changes` is set to `true`.
2. ChangeRequest cannot skip statuses. Flow: pending → sent_to_supplier → supplier_confirmed → applied. Or: pending → sent_to_supplier → supplier_rejected (then manager decides: cancel the change or negotiate).
3. When status becomes `applied`: the actual Trip and related entities (TripStop, TripCostItem, etc.) are updated with `new_values`. `trip.has_pending_changes` is recalculated (false only if NO other pending/sent ChangeRequests exist for this trip).
4. `old_values` / `new_values` are JSON snapshots. Example:
   ```json
   {
     "old_values": { "time_start": "08:00", "pax_count": 45 },
     "new_values": { "time_start": "06:00", "pax_count": 50 }
   }
   ```
5. If price changes: new TripCostItem lines may need to be created or existing ones updated. The ChangeRequest stores the delta for quick visibility.
6. Dashboard must prominently surface trips with `has_pending_changes = true` and ChangeRequests in `pending` or `sent_to_supplier` status.

---

### Financial Entities

#### Invoice (to Client)
```
Invoice {
  id              UUID PK
  order_id        UUID FK → Order
  invoice_number  String UNIQUE     // "INV-2026-0187"
  date_issued     Date
  due_date        Date
  currency        String
  subtotal        Decimal           // sum of line items before tax
  tax_total       Decimal
  total           Decimal           // subtotal + tax
  status          Enum: draft | sent | partially_paid | paid | overdue | cancelled | credit_note
  type            Enum: deposit | regular | final | credit_note
  notes           Text?
  pdf_path        String?
  stripe_payment_intent_id String?  // Stripe PaymentIntent ID
  stripe_payment_link_url  String?  // shareable payment URL
  created_at      DateTime
  updated_at      DateTime
}
```
**Business rules**:
- `invoice_number` auto-generated, sequential per year.
- Status `overdue` is set automatically when `due_date < today AND status IN (sent, partially_paid)`.
- A `credit_note` type invoice has negative amounts and references the original invoice.
- `total` = sum of InvoiceLineItem amounts. Stored for performance but must be recalculated if line items change.

#### InvoiceLineItem
```
InvoiceLineItem {
  id            UUID PK
  invoice_id    UUID FK → Invoice
  trip_id       UUID? FK → Trip    // nullable for non-trip charges (admin fee, deposit)
  description   String              // "Transfer CDG → Hotel, 45 pax, June 15"
  quantity      Decimal default 1
  unit_price    Decimal
  amount        Decimal             // quantity × unit_price
  tax_rate      Decimal default 0
  tax_amount    Decimal default 0
  sort_order    Int default 0
}
```
**Business rules**:
- When `trip_id` is set, the trip is considered "invoiced" for client-side tracking.
- A trip can appear in multiple invoices (e.g., deposit invoice + final invoice), but this should be tracked to avoid double-invoicing the full amount.

#### ClientPayment
```
ClientPayment {
  id          UUID PK
  invoice_id  UUID? FK → Invoice   // nullable for unallocated deposits
  order_id    UUID FK → Order
  date        Date
  amount      Decimal
  currency    String
  method      Enum: bank_transfer | card | sepa | cash | stripe | other
  reference   String?              // bank transaction ref
  stripe_charge_id String?         // Stripe charge/payment ID for auto-reconciliation
  type        Enum: deposit | regular | final | refund
  notes       String?
  created_at  DateTime
}
```
**Business rules**:
- Invoice status auto-updates: if SUM(payments.amount) >= invoice.total → paid; if SUM > 0 but < total → partially_paid.
- Deposits (type=deposit) with invoice_id=null sit "on account" for the order. When generating next invoice, outstanding deposit balance can be deducted.

#### SupplierBill
```
SupplierBill {
  id            UUID PK
  supplier_id   UUID FK → Supplier
  bill_number   String              // supplier's own invoice number
  date_received Date
  due_date      Date
  currency      String
  total         Decimal
  status        Enum: received | verified | approved | partially_paid | paid | disputed
  dispute_notes Text?
  pdf_path      String?
  notes         String?
  created_at    DateTime
  updated_at    DateTime
}
```
**Business rules**:
- `disputed` status: supplier charged more than agreed purchase price. `dispute_notes` captures the reason.
- `verified` means a manager checked line items against trip purchase prices.
- `approved` means ready for payment.

#### SupplierBillLineItem
```
SupplierBillLineItem {
  id                UUID PK
  supplier_bill_id  UUID FK → SupplierBill
  trip_id           UUID? FK → Trip
  description       String
  amount            Decimal
}
```

#### SupplierPayment
```
SupplierPayment {
  id                UUID PK
  supplier_bill_id  UUID FK → SupplierBill
  date              Date
  amount            Decimal
  currency          String
  method            Enum: bank_transfer | card | cash | other
  reference         String?
  notes             String?
  created_at        DateTime
}
```

---

### Supporting Entities

#### Document
Polymorphic file attachment.
```
Document {
  id              UUID PK
  entity_type     String           // "Order", "Trip", "Invoice", "SupplierBill"
  entity_id       UUID
  type            Enum: contract | voucher | confirmation | photo | invoice_scan | itinerary | other
  filename        String
  file_path       String
  mime_type       String
  file_size       Int
  uploaded_by     UUID FK → User
  created_at      DateTime
}
```

#### ActivityLog
Automatic audit trail on every data mutation.
```
ActivityLog {
  id            UUID PK
  entity_type   String           // "Trip", "Order", "TripCostItem", etc.
  entity_id     UUID
  action        Enum: created | updated | deleted
  field_changes  Json?           // { "status": { "old": "draft", "new": "confirmed" } }
  performed_by  UUID FK → User
  performed_at  DateTime
  ip_address    String?
}
```
**Business rules**:
- Written automatically by Prisma middleware or application-level hooks on every create/update/delete.
- NEVER deleted. Append-only table.
- `field_changes` stores only changed fields with old/new values, not full entity snapshots (to keep size manageable).

---

## Key Computed Values (Not Stored, Calculated On Read)

```
Trip:
  sale_total      = SUM(TripCostItem.amount + tax_amount WHERE direction=sale)
  purchase_total  = SUM(TripCostItem.amount + tax_amount WHERE direction=purchase)
  margin          = sale_total - purchase_total
  margin_percent  = (margin / sale_total) × 100
  is_invoiced     = EXISTS(InvoiceLineItem WHERE trip_id = this.id)
  is_billed       = EXISTS(SupplierBillLineItem WHERE trip_id = this.id)

Order:
  total_sale      = SUM(all trips' sale_total)
  total_purchase  = SUM(all trips' purchase_total)
  total_margin    = total_sale - total_purchase
  trip_count      = COUNT(trips)
  completed_trips = COUNT(trips WHERE status=completed)
  pending_changes = COUNT(trips WHERE has_pending_changes=true)
  
  client_invoiced = SUM(Invoice.total WHERE order_id=this.id AND status != cancelled)
  client_paid     = SUM(ClientPayment.amount WHERE order_id=this.id)
  client_balance  = client_invoiced - client_paid
  
  supplier_billed = SUM(SupplierBillLineItem.amount for trips in this order)
  supplier_paid   = SUM(SupplierPayment.amount for bills containing trips in this order)
  supplier_balance = supplier_billed - supplier_paid

Invoice:
  paid_amount     = SUM(ClientPayment.amount WHERE invoice_id=this.id)
  remaining       = total - paid_amount

SupplierBill:
  paid_amount     = SUM(SupplierPayment.amount WHERE supplier_bill_id=this.id)
  remaining       = total - paid_amount
```

---

## Key Business Processes & Workflows

### 1. Quote Flow (NEW)
1. Client inquiry comes in (via Missive).
2. Manager creates Quote: selects client, adds line items (trip descriptions + prices), sets valid_until date.
3. Quote PDF generated from template → sent to client (via Missive — copy email text).
4. Client accepts → Quote status → accepted.
5. Manager converts Quote to Order: system creates Order + Trips from quote line items. Quote status → converted, `converted_to_order_id` set.
6. If client rejects → status → rejected with notes. If no response and valid_until passes → expired.

### 2. Order Creation Flow
1. Manager creates Order (from Quote conversion or directly), status=draft, with client selection.
2. Adds Trips with basic info: dates, type, pax, stops, flight details, meeting sign, luggage.
3. For each trip: adds TripCostItems (sale side — what client will pay).
4. Order status → confirmed when client confirms (e.g., signs contract).

### 3. Supplier Assignment Flow
1. Manager selects a Supplier for a Trip based on: region coverage, availability, price, rating.
2. Negotiates purchase price → adds TripCostItems (purchase side).
3. Supplier confirms availability → Trip status → supplier_assigned. (Supplier can also confirm via Supplier Portal.)
4. Supplier provides Vehicle(s) and Driver(s) details → TripVehicle, TripCrew created → Trip status → vehicle_assigned. (Supplier can enter these via Supplier Portal self-service.)
5. When all details filled (vehicle, crew, contacts, stop times) → status → ready_for_service.

### 4. Change Management Flow (CRITICAL PATH)
1. Client requests change (email/phone/portal).
2. Manager creates ChangeRequest: logs what changed, old/new values.
3. Trip gets `has_pending_changes = true`.
4. Manager contacts supplier about the change → ChangeRequest status → sent_to_supplier.
5a. Supplier confirms → status → supplier_confirmed. Manager applies changes → status → applied. Trip/TripStop/TripCostItem updated. `has_pending_changes` recalculated.
5b. Supplier rejects → status → supplier_rejected. Manager negotiates or cancels the ChangeRequest.
6. If price impact: new TripCostItems added or existing updated. Both purchase and sale sides.

### 5. Trip Execution Flow
1. Day before: Trip status → dispatched (briefing sent to driver via supplier or Supplier Portal).
2. Trip starts: status → in_progress. Dispatcher monitors via Mission Control.
3. If issue arises: dispatcher sets `has_issue = true` + `issue_description`. Trip appears in Issues filter.
4. Trip ends: status → completed. Actual times filled in TripStop and TripCrew.

### 6. Client Invoicing Flow
1. Manager creates Invoice for an Order, selects which Trips to include.
2. InvoiceLineItems auto-populated from Trip sale TripCostItems (can be edited).
3. Invoice PDF generated from template.
4. Invoice status → sent (PDF emailed to client via Missive, also visible in Customer Portal).
5. Stripe payment link generated and attached to invoice. Client can pay via portal or payment link.
6. ClientPayments recorded automatically (Stripe webhook) or manually. Invoice status auto-updates.

### 7. Supplier Billing Flow
1. Supplier sends their invoice → uploads via Supplier Portal or manager creates SupplierBill manually, attaches PDF scan.
2. Manager adds SupplierBillLineItems, linking to Trips.
3. Manager verifies: do line item amounts match Trip purchase TripCostItems? If not → disputed.
4. If ok → approved → SupplierPayments recorded as they are made.

---

## Dashboard & Key Views (Data Requirements)

### Main Dashboard (Internal)
- **Today/Tomorrow trips**: list with status, supplier, vehicle, driver, pax, pickup time/location.
- **Pending changes**: count + list of ChangeRequests in pending/sent_to_supplier status.
- **Unassigned trips**: trips in confirmed status without supplier.
- **Overdue invoices**: invoices past due_date, not fully paid.
- **Issues**: trips with `has_issue = true`.
- **Financial summary**: this month's total revenue, costs, margin.
- **Open quotes**: quotes in sent status approaching valid_until.

### Mission Control (Dispatching Dashboard)
Dedicated real-time operational view for dispatchers. Designed for 24/7 operations monitoring.

**Filters**: Today | Tomorrow | Next 7 days | Active now | Issues only | By country | By supplier | By customer.

**Columns displayed per trip**: Trip ID, Service date/time, Status, Customer, Pickup location, Drop-off location, Supplier, Driver name + phone (click-to-call), Tour leader name + phone, Flight number, Delay flag, Issue flag.

**Dispatcher actions** (available inline, no page navigation):
- Change trip status (quick-select dropdown).
- Toggle issue flag + enter issue description.
- Add operational note (TripNote type=internal).
- Click-to-view: driver contact, supplier contact, customer contact.
- Quick view of trip stops with times.

**Auto-refresh**: page auto-refreshes every 60 seconds or uses server-sent events for live updates.

### Order Detail View
- Order header (client, dates, status, manager, quote reference if from quote).
- All trips in a table/timeline with key info + status badges.
- Financial summary: planned sale, planned purchase, margin, invoiced, paid, outstanding.
- Documents list.
- Activity log for this order.

### Trip Detail View
- Full trip info with all child entities displayed in sections: Stops (map + list), Vehicles, Crew, Contacts, Cost Items (purchase/sale side by side), Notes, Change History, Documents.
- Status progression bar.
- Quick actions: assign supplier, add vehicle, add driver, create change request, mark as completed.

### Supplier Workload View
- Calendar/timeline showing all trips assigned to a supplier.
- Which vehicles and drivers are busy on which dates.
- Conflict detection (same driver/vehicle on overlapping trips).

### Financial Overview
- By order: revenue, cost, margin, invoiced %, paid %.
- By supplier: billed, paid, outstanding.
- By period: monthly revenue, costs, margin trend.
- Accounts receivable (client) and accounts payable (supplier) aging.

---

## Customer Portal

Separate auth flow (ClientContact with portal_access=true). Clean, simple interface — NOT the full internal app.

### Customer can see:
- **Active bookings**: list of trips in current/future orders with status, dates, pickup/dropoff, vehicle type, driver name + phone (when assigned), tour leader info.
- **Historical bookings**: completed trips.
- **Booking confirmations**: generated PDF per trip or order.
- **Invoices**: list with status (paid/unpaid/overdue), PDF download, payment link.
- **Payment status**: what's paid, what's outstanding, payment history.

### Customer NEVER sees:
- Supplier company name, supplier costs, purchase prices.
- Internal margins.
- Internal notes (TripNote type=internal).
- Other customers' data.
- Issue flags or dispatcher notes.

### Customer portal technical notes:
- Route group: `/portal/customer/[clientId]/*`
- Auth middleware checks: `user.type === 'client_contact'` AND `user.client_id === requested_resource.client_id`.
- Every query MUST filter by client_id. No exceptions.

---

## Supplier Portal

Separate auth flow (SupplierContact with portal_access=true). Focused on operational fulfillment.

### Supplier can see (for their assigned trips only):
- **Assigned bookings**: trips assigned to them with dates, pickup/dropoff, flight details, tour leader contact, pax count, vehicle requirements, meeting sign text, special instructions.
- **TripNotes** with type=for_supplier or type=for_driver.
- **Booking documents**: vouchers, itineraries shared by BCS.

### Supplier can DO:
- **Confirm assignment**: changes trip from supplier_assigned → acknowledged (or stays supplier_assigned with a confirmed_by_supplier flag).
- **Enter driver details**: name, phone, languages → creates/updates TripCrew.
- **Enter vehicle details**: plate number, type, capacity → creates/updates TripVehicle.
- **Upload their invoice**: file attached as Document + optionally creates draft SupplierBill.
- **Leave comments**: creates TripNote with type=from_supplier (new type, visible to internal team).

### Supplier NEVER sees:
- Client sale prices, margins.
- Client company internal details.
- Other suppliers' assignments.
- Internal notes or issue flags.
- Financial data beyond their own bills/payments.

### Supplier portal technical notes:
- Route group: `/portal/supplier/[supplierId]/*`
- Auth: `user.type === 'supplier_contact'` AND `user.supplier_id === requested_resource.supplier_id`.
- Every query MUST filter by supplier_id. No exceptions.

---

## Stripe Integration

### Payment link generation:
- From any Invoice, manager clicks "Generate Payment Link".
- System creates Stripe PaymentIntent or Checkout Session for invoice.total.
- Supports: Credit Card, SEPA Direct Debit (EUR), ACH (USD if needed).
- Payment link URL stored on Invoice record. Shareable via email or Customer Portal.

### Webhook handling:
- Stripe webhook endpoint receives payment events.
- On `checkout.session.completed` or `payment_intent.succeeded`: auto-create ClientPayment record, update Invoice status.
- On `charge.refunded`: create ClientPayment with type=refund (negative amount).
- On `payment_intent.payment_failed`: log event, do NOT auto-update (manual review).

### Payment features:
- **Deposit handling**: generate payment link for partial amount (deposit). Recorded as ClientPayment type=deposit.
- **Partial payments**: Stripe handles partial; system records each payment separately.
- **Overdue tracking**: daily cron marks invoices overdue when due_date < today and not fully paid.
- **Manual payment entry**: not all payments come via Stripe. Manual entry for bank transfers, cash, etc.

### Stripe metadata:
- Every Stripe PaymentIntent includes metadata: `{ invoice_id, order_id, client_id }` for reconciliation.

---

## PDF Document Generation

Automatic PDF generation from templates for:

1. **Quote PDF** — company branding, client details, line items, total, valid until, terms.
2. **Booking Confirmation** — per order or per trip: dates, stops, vehicle type, pax, special instructions. Client-safe (no supplier costs).
3. **Customer Invoice** — professional invoice with line items, tax, total, payment terms, Stripe payment link QR code.
4. **Supplier Order** — trip details for supplier: stops, times, pax, flight info, meeting sign, tour leader contact. Supplier-safe (no client prices).
5. **Driver Briefing** — compact one-page: stops with times, pax, tour leader phone, meeting sign, special instructions, TripNotes type=for_driver.
6. **Payment Receipt** — generated after payment confirmed.

### Technical approach:
- Use a headless template engine (React-PDF or Puppeteer HTML→PDF).
- Templates stored as React components or HTML templates with variable slots.
- Generated PDFs stored in filesystem, path saved in `pdf_path` field on relevant entity.
- "Generate PDF" button on relevant entity pages. "Copy link" for sharing. "Open in Missive" deep-link.

---

## Reporting Module

### Dashboard reports (simple, read-only views):
- Revenue per month (chart + table).
- Gross margin per month.
- Bookings (trips) per month.
- Revenue by customer (top 10 + full table).
- Revenue by supplier (top 10 + full table).
- Outstanding customer payments (aging: 0-30, 31-60, 61-90, 90+ days).
- Outstanding supplier invoices (same aging).
- Margin per booking (trip-level, sortable).
- Average margin %.
- Active bookings count.
- Issue count (trips with has_issue=true).
- Top 10 customers by revenue.
- Top 10 suppliers by volume.

### Export:
- All report tables exportable as CSV and XLSX.
- PDF export is optional for V1 (nice to have).

### Filters on all reports:
- Date range (custom, this month, last month, this quarter, this year).
- By customer, by supplier, by manager.

---

## Validation & Business Constraints

1. Trip date_start cannot be in the past (when creating, not editing completed trips).
2. TripStop sequence must be continuous (1, 2, 3 — no gaps).
3. TripCostItem amount can be negative ONLY for category=discount.
4. Invoice total must equal SUM of its line items (enforced on save).
5. ChangeRequest status transitions are strict — see flow above.
6. Order cannot be deleted if it has any Invoice with status != draft.
7. Supplier/Vehicle/Driver soft-delete only (is_active=false), never hard delete — they may be referenced by historical trips.
8. All monetary amounts stored as Decimal(12,2).
9. ActivityLog is append-only, no updates or deletes ever.
10. Unique constraints: Order.order_number, Invoice.invoice_number, Quote.quote_number, (Trip.order_id + Trip.trip_number), (TripStop.trip_id + TripStop.sequence).
11. Customer Portal queries MUST always filter by client_id. A client can NEVER access another client's data.
12. Supplier Portal queries MUST always filter by supplier_id. A supplier can NEVER access another supplier's data or client-facing financial data.
13. Quote valid_until: auto-expire quotes where valid_until < today and status = sent.
14. Role-based access: every API endpoint must check user.role before processing. Sales role cannot see supplier purchase prices. Dispatcher role has read-only financial access.

---

## API Structure (Next.js App Router)

```
# Internal API (requires internal auth)
/api/clients           — CRUD
/api/clients/[id]/contacts — CRUD
/api/suppliers         — CRUD
/api/suppliers/[id]/contacts — CRUD
/api/suppliers/[id]/vehicles — CRUD
/api/suppliers/[id]/drivers — CRUD
/api/quotes            — CRUD + list with filters
/api/quotes/[id]/line-items — CRUD
/api/quotes/[id]/convert — POST: convert quote to order
/api/quotes/[id]/pdf   — POST: generate PDF
/api/orders            — CRUD + list with filters
/api/orders/[id]/trips — CRUD + bulk create
/api/trips/[id]        — CRUD
/api/trips/[id]/stops  — CRUD + reorder
/api/trips/[id]/vehicles — CRUD
/api/trips/[id]/crew   — CRUD
/api/trips/[id]/contacts — CRUD
/api/trips/[id]/costs  — CRUD
/api/trips/[id]/notes  — CRUD
/api/trips/[id]/changes — CRUD + status transitions
/api/trips/[id]/documents — CRUD + file upload
/api/trips/[id]/issue  — PATCH: set/clear issue flag (dispatcher)
/api/invoices          — CRUD + list
/api/invoices/[id]/line-items — CRUD
/api/invoices/[id]/payments — CRUD
/api/invoices/[id]/pdf — POST: generate PDF
/api/invoices/[id]/payment-link — POST: create Stripe payment link
/api/supplier-bills    — CRUD + list
/api/supplier-bills/[id]/line-items — CRUD
/api/supplier-bills/[id]/payments — CRUD
/api/dashboard         — aggregated dashboard data
/api/dispatch          — Mission Control data (filtered trip list with joins)
/api/activity-log      — read-only, filtered by entity
/api/reports/[type]    — reporting endpoints (revenue, margin, aging, etc.)
/api/reports/export    — CSV/XLSX export

# Stripe webhook
/api/webhooks/stripe   — Stripe event handler (no auth, signature verification)

# Customer Portal API (requires client_contact auth, scoped to client_id)
/api/portal/customer/bookings      — list trips for this client
/api/portal/customer/bookings/[id] — trip detail (client-safe fields only)
/api/portal/customer/invoices      — list invoices for this client
/api/portal/customer/invoices/[id] — invoice detail + PDF
/api/portal/customer/payments      — payment history

# Supplier Portal API (requires supplier_contact auth, scoped to supplier_id)
/api/portal/supplier/bookings      — list assigned trips
/api/portal/supplier/bookings/[id] — trip detail (supplier-safe fields only)
/api/portal/supplier/bookings/[id]/confirm — POST: confirm assignment
/api/portal/supplier/bookings/[id]/crew    — POST: enter driver details
/api/portal/supplier/bookings/[id]/vehicle — POST: enter vehicle details
/api/portal/supplier/bookings/[id]/comment — POST: leave comment (TripNote from_supplier)
/api/portal/supplier/invoices      — upload supplier invoice
```

All list endpoints support: pagination (cursor-based), sorting, filtering by status/date/entity.

---

## Development Phases

### Phase 1: Foundation
- Database schema (Prisma), migrations, seed data with realistic test data.
- Auth system: NextAuth for internal users (User model), login page.
- Layout shell: sidebar nav, header with user info, main content area.
- Client CRUD with contacts. Supplier CRUD with contacts, vehicles, drivers.
- Role-based middleware (check user.role on every API route).

### Phase 2: Quotes & Orders
- Quote CRUD with line items. Quote PDF generation.
- Quote → Order conversion flow.
- Order CRUD with trip management.
- Trip full lifecycle: stops, vehicles, crew, contacts, cost items, notes.
- Trip status management with transition rules and validation.
- ChangeRequest full workflow.

### Phase 3: Financial
- Invoice generation from order/trips with line items.
- Invoice PDF generation.
- Stripe integration: payment link creation, webhook handler.
- ClientPayment tracking (auto from Stripe + manual entry), invoice auto-status updates.
- SupplierBill management with line items.
- SupplierPayment tracking.
- Financial computed values and summaries on order/trip detail views.

### Phase 4: Mission Control & Dashboard
- Main internal dashboard with all widgets.
- Mission Control (Dispatching) — dedicated view with filters, inline actions, auto-refresh.
- Order detail view with financial summary.
- Supplier workload calendar.
- Activity log viewer.

### Phase 5: Portals
- Customer Portal: separate auth, bookings view, invoices, payment links, confirmations.
- Supplier Portal: separate auth, assigned bookings, confirm/enter details, upload invoices, comments.
- Strict data scoping (client_id / supplier_id filtering on every query).
- Portal-specific layouts (simpler than internal app).

### Phase 6: Reporting & Polish
- Reporting module: all dashboard reports, date range filters, CSV/XLSX export.
- PDF generation for remaining templates (supplier order, driver briefing, payment receipt).
- Document upload/management.
- Bulk operations (duplicate trip, bulk status update).
- Global search across all entities.
- Overdue invoice cron job. Quote expiry cron job.

---

## Code Style & Conventions

- Use Prisma for all DB access. No raw SQL unless for complex aggregations.
- Server Actions for mutations where possible.
- Zod schemas for all input validation (shared between client and server).
- Error handling: try/catch with typed errors, user-friendly messages.
- All list pages: server-side pagination, URL-based filters (searchParams).
- All forms: optimistic UI with server validation.
- Consistent naming: camelCase in code, snake_case in DB.
- Every entity CRUD follows the same pattern: list page, detail page, create/edit modal or page.
- Prisma middleware for ActivityLog: automatically logs all changes.
- Date display: use date-fns with user timezone from browser.
- Currency display: Intl.NumberFormat with proper currency code.
- Stripe SDK: use `stripe` npm package server-side only. Never expose secret keys to client.
- PDF generation: React-PDF or Puppeteer. Templates as React components.

---

## Missive Integration Hooks

ManagedCharter does NOT build its own email/messaging. Missive remains the communication layer. Integration points:

- **"Copy email text" button** on Quote, Booking Confirmation, Invoice: generates formatted plain text summary suitable for pasting into Missive compose.
- **"Open in Missive" link** where possible: deep-link to Missive conversation if a Missive conversation ID is stored on the entity.
- **Missive conversation ID** (optional field on Order and Trip): allows linking back to the email thread.
- No API integration with Missive in V1. Just copy-paste helpers and optional ID linking.

---

## Explicitly NOT in V1

These features are out of scope for the MVP:
- Public marketplace or directory.
- Built-in chat or email system (Missive handles this).
- Mobile app (responsive web is sufficient).
- AI-powered supplier matching or dynamic pricing.
- Complex accounting system or general ledger.
- Multi-tenant / white-label.
- Real-time GPS tracking of vehicles.
- Automated Missive API integration (V2 candidate).