Skip to content

Trips to Deliver

Trips to Deliver is a supplier-facing Filament resource that surfaces the bookings a supplier needs to deliver. It is backed by the Booking model but exposes only the operational information a supplier cares about — passengers, hotels, activities, transfers, pick-up details — and explicitly hides customer pricing, payment status, sales attribution, and the rest of the checkout funnel. The one money figure it does show is the supplier’s own Land cost, added for Last Seats campaigns.

Volāre staff continue to use the full BookingResource at /admin/bookings. Suppliers do not get access to that resource.

Only supplier-managers (User::isSupplierManager()). Navigation, list, and view are all gated through that check; create / edit / delete are disabled at the resource level.

Two filters are applied in getEloquentQuery() (and reused for route binding in getRecordRouteBindingEloquentQuery()):

  1. Supplier scopingwhereHas('offer.supplierTourRate.tour.suppliers', ...) restricted to the manager’s supplier_id. A multi-supplier tour is visible to all attached suppliers.
  2. Status filter — only post-payment, committed statuses are visible:
    • PendingFlightBooking
    • FlightBookingInProgress
    • FlightBookingFailed
    • FlightsConfirmed
    • PendingLandConfirmation
    • Confirmed
    • AwaitingBalance
    • FullyPaid
    • Completed

Bookings still in Draft, Checkout, PendingPayment, PaymentProcessing, and the terminal negative outcomes (Cancelled, Expired) are hidden — suppliers only see trips a customer has actually paid for and committed to.

Default list sort is booked_at desc.

Source: backend/app/Filament/Resources/TripsToDeliver/TripToDeliverResource.php

The view page renders an infolist with the trip’s services pulled from the offer’s tour itinerary (hotels, activities, transfers), arrival pickup info, contract link, allotment, and a “Stop sale” badge derived from Offer::isStopped() against any active stop sale on the tour.

Flight info source of truth: flight times and routes are read from the offer’s bound DynamicFlightCache (via Offer::offerFlights → flightCache → segments), not from Booking::flight_selection. The cache binding is the canonical state of what was actually booked; flight_selection may be stale or incomplete.

Source: backend/app/Filament/Resources/TripsToDeliver/Schemas/TripToDeliverInfolist.php

The Land cost section is the only money a supplier sees anywhere in Arkana. It exists for one reason: a departure sold under a Last Seats campaign is a departure the supplier agreed to charge less for, so they must be able to check the figure.

Visibility gate: the section renders whenever SupplierLandCost::forBooking() returns a breakdown. That needs all of:

  1. the offer resolves to a supplier tour (offer.supplierTourRate.tour);
  2. PackageSlotResolver resolves a non-empty slot list for the departure date — it returns null when any slot has no member serving that date, and an empty list when the tour has no package services;
  3. the signed-in supplier owns at least one of those slots’ services, with a positive price for the booking’s room type.

Condition 2 is the surprising one: a blackout or a rate gap on any one slot — including another supplier’s — hides the whole Land cost section, discount row included, on a campaign booking.

The discount row inside the section is gated separately, and appears only when the booking carries a last_seats_seat_consumptions row.

What it shows, and nothing else:

Row Content Shown when
Services The supplier’s own package services, with pax count and amount Always (the section’s own gate)
Land cost Their subtotal Always
Discount The campaign title, its percentage as a badge, and the money off Only on a campaign booking
Amount to pay Subtotal minus the discount (equal to the subtotal without one) Always

Rules that make the figure defensible:

  • Their contract currency, not the euros Volāre sells in — amounts are taken from the supplier’s own rate prices before FX conversion.
  • Their services only. On a tour operated by two DMCs, each supplier sees only the services they provide and a discount computed on their own subtotal, so nobody is shown — or charged — a discount on a colleague’s work. A supplier with no services on the tour gets no section at all.
  • Partly frozen terms. The percentage and seat count come from the booking’s last_seats_seat_consumptions row, captured at finalization, so editing or ending a campaign does not move them. The title is read from the live period (there is no title column on the consumption row), and the amounts are re-derived from the supplier’s current rate prices rather than from the frozen land_discount_amount, which is stored in the market currency and never read here. Renaming a campaign, or changing the supplier’s rates, therefore does change what this screen shows for a past booking.
  • Package services only. The subtotal sums the tour’s package cost slots; the extra-night supplement is not part of it (and is not discounted, which the section states).
  • Partial quota is stated. When the campaign seats covered only part of the party, the helper text reads “Applies to N of M travellers”, so an amount below the headline percentage reads as intended rather than as an error. Which packages the booking includes is resolved by PackageSlotResolver — the same class AutoOfferGeneratorService::calculateLandPrice() uses — so the supplier’s breakdown and the customer’s price can never disagree about the contents of a departure.

Source: backend/app/Services/LastSeats/SupplierLandCost.php

  • Sidebar — “Suppliers” group, “Trips to Deliver” (/admin/trips-to-deliver)
  • Email CTA — the BookingSubmittedToSupplierNotification “Open trip” action links directly to the resource’s view page
  • Filament bell — the database notification’s “Open trip” action does the same

See Notifications — Booking Submitted.