Quotations
Service Prices
In the sidebar, Service Prices is an expandable group with two children:
- View Prices — a read-only, searchable reference page open to all staff, with Visa and Tours pill tabs. Visa shows every (country, visa type, entry type, sticker visa) pricing template with its fee breakdown; Tours shows every itinerary template’s per-adult/per-child price. No edit controls here at all.
- Add Template — owner/manager only, the actual management page: the same Visa/Tours tabs, but with create/edit/delete for both catalogs.
Visa pricing templates
Each visa pricing template defines a Visa Fee and Consulting Fee (always present) plus any number of admin-defined optional extra fields, and an optional notes override, keyed by (country, visa type, entry type, sticker visa).
Templates are hard-deleted, not soft-deleted, so a retired combination can be recreated immediately without hitting a stale uniqueness conflict. Historical quotes that referenced a deleted template keep their own frozen line items — they just lose the back-link to the template.
Tour itinerary templates
Itinerary templates (see Fleet & Tours) can optionally carry a price per adult and price per child. When staff issue an invoice for a tour using a priced template, the invoice auto-fills a locked line item per party type from that price and the tour’s party size — the same auto-fill mechanism visa pricing templates already provide for visa invoices.
Quote wizard
“Generate Quote,” available from both the dashboard and the Quotations page, opens a small chooser first: Existing Customer or Walk-in / No Customer. Either choice leads into a 5-step flow:
- Pick customer (existing-customer flow) or enter a plain contact
name/email/phone (walk-in flow — no
CustomerEntitycreated) - Pick service type — Visa, Ticket, or Tour
- Pick the customer’s specific request (visa/ticket, existing-customer flow only) or type the details manually (walk-in flow, or Tour either way — see below)
- Auto-populated (visa, matched against the pricing catalog) or manually-entered (ticket) pricing fields, fully editable, with support for custom fields
- Live HTML preview before generating
Tour quotes never require an existing tour inquiry — staff pick one of
the priced itinerary templates (auto-fills adult/child line items from its
rate × the entered party size) or build a fully manual list of places and
their charges. When building a tour manually, an interactive location autocomplete
dropdown automatically populates from admin-configured system lookup categories (TOURIST_LOCATION, LOCATION, DESTINATION from Reference Data) for fast, consistent entry.
This is true whether the quote is for an existing customer or a walk-in.
Walk-in quotes are a separate underlying record from customer quotes — not a customer-optional version of the same one — so a prospect who hasn’t been onboarded yet can still get a quote for any of the three service types. A walk-in quote’s contact email is optional; without one, the quote can still be generated and its PDF downloaded, just not emailed. The Quotations list shows both kinds of quote together, with a “Walk-in” badge marking the customerless ones.
No email on file, either kind of quote — the “Confirm & Send” button (in the wizard’s final step) and the “Resend” action on the Quotations list are both disabled/hidden whenever there’s no email to send to, so staff never hit a dead-end error trying to email a quote with nowhere to send it — download the PDF instead.
Lifecycle
DRAFT (editable) → GENERATED (PDF rendered, pricing frozen, number assigned) → SENT (emailed, resendable)Drafts can be edited or reopened from the Quotations list. Quotes in any status (DRAFT, GENERATED, SENT, ACCEPTED, REJECTED) can be soft-deleted by authorized staff or admins, cleanly removing them from quote lists and reports while preserving backend audit records (deleted_at).
Auto-expiry
Quotes carry a 3-day expiry from creation and are filtered out of every read path automatically once expired — no background job needed for the filtering itself (a separate daily cleanup job later soft-deletes the expired rows; see Deployment & CI/CD).
Multi-currency
Staff can pick a currency (LKR, USD, EUR, GBP, AUD, INR) on the pricing step. The exchange rate is fetched and locked in permanently at creation time — it’s never re-applied later, so a sent quote’s price never silently drifts if rates change afterward. Rates are cached server-side for an hour to protect the free-tier exchange-rate API quota, and cached client-side for up to 3 days.