Skip to content

Travel (Raj Pravas) — Technical Reference

Part of the Aashray Technical Reference. Pairs with Business Logic — Travel, which describes these rules in plain terms with no code detail. Related technical files: Booking Lifecycle, Adhyayan & Utsav, Payments, Admin Panel Map, API Reference, Status & Cron.

This file is 100% derived from reading the backend, admin, and app source on main — no operational/staff-knowledge content lives here (that's in the business-logic companion). It covers exactly how travel booking, bus assignment, and the coordinator role are implemented: every route, the data model, admin mechanics, edge cases, and known defects.

Verification status: audited & anchored per docs/DOCS-METHODOLOGY.md on 2026-07-07 against aashray-backend main. Every rule carries a [src: …] anchor. This pass caught real drift — the doc was written against an older backend: (a) guest travel creation now exists (bookTravelGuest); the old "no guest travel path" claim (former Discrepancy #10) was false and is corrected; (b) round-trip travel is now implemented (bookRoundTripTravel, trip_group_id, return_date) and was entirely undocumented — a new section below covers it; (c) minor field-level fixes (leaving_post_adhyayan is not set at creation nor editable via updateBooking; updatedBy at creation is the cardno; the model has a trip_group_id column; a GET /travel/events route exists).


Where travel is created (not under /travel)

The /api/v1/travel routes expose only fetch and cancel. Travel creation flows through the unified/mumukshu booking engine: - POST /api/v1/mumukshu/validatevalidateBookingcheckTravelAvailability(data) for booking_type = 'travel'. - POST /api/v1/mumukshu/bookingmumukshuBooking → dispatch case TYPE_TRAVELbookTravel → helper bookTravelForMumukshus. - This single path serves both Self and Mumukshu (Self is just a one-person group). The legacy /api/v1/unified/{validate,booking} endpoints now throw 410 ("Please update Aashray app…").

checkTravelAvailability (validate step) — mumukshuBooking.controller.js

  • Rejects a past date (ERR_INVALID_DATE).
  • validateCards(mumukshus) — every card must exist.
  • Enforces XOR here: for each group, if pickup_point !== RESEARCH_CENTRE && drop_point !== RESEARCH_CENTRE → 400 "Travel must be either to or from Research Centre".
  • Calls checkTravelAlreadyBooked (see below).
  • Returns { status: 'awaiting confirmation', charge: 0 } — travel is always free at validate time. [src: helpers/travelBooking.helper.js:252-277]

checkTravelAlreadyBookedhelpers/travelBooking.helper.js

For the group's mumukshus and drop_point, it computes direction (isToResearchCentre = drop_point === RESEARCH_CENTRE) and looks for any existing booking on the same date, same direction in status confirmed / waiting / pending / awaiting confirmation. Direction match is: to-RC → pickup != RC AND drop = RC; from-RC → pickup = RC AND drop != RC. If found → 400 "Travel already booked for this direction on the selected date". So a member may hold at most one to-RC and one from-RC booking per date. [src: helpers/travelBooking.helper.js:32-52]

bookTravelForMumukshus (creation) — helpers/travelBooking.helper.js

  • Re-checks past date and validateCards, and re-runs checkTravelAlreadyBooked per group.
  • For each mumukshu in each group, inserts a travel_db row with bookingid = uuidv4(), cardno = mumukshu, bookedBy = (booker if booker ≠ mumukshu else null), status = 'awaiting confirmation', plus date, type, pickup_point, drop_point, luggage, arrival_time, total_people (default 1), comments, and updatedBy = user.cardno (not the literal 'USER'). Note: leaving_post_adhyayan is not written at creation — the row relies on the column default.
  • Returns { userBookingIds, waitingBookingCount: 0 }travel is never auto-waitlisted at creation; every new leg is awaiting confirmation and carries no charge/transaction. [src: helpers/travelBooking.helper.js:132-202 (bookingid :177, bookedBy :181, status :182, updatedBy :193, return :201)]

Round-trip travel — bookRoundTripTravel (both self/mumukshu and guest)

Round trips are supported: when the request carries a return_date and a returnMumukshuGroup, bookTravel dispatches to bookRoundTripTravel, which creates the onward and return legs sharing a common trip_group_id (so the two legs are linked, unlike two independently-made one-way bookings). checkTravelAvailability validates the return leg too, including rejecting a return date before the onward date ("Return date cannot be before the onward date"). [src: helpers/travelBooking.helper.js:204-247; controllers/client/mumukshuBooking.controller.js:560-568; controllers/client/guestBooking.controller.js:548-558; helpers/travelBooking.helper.js:270-275 (return-date validation)]

Status model & the waitlist

travel_db.status is an ENUM of exactly: waiting, awaiting confirmation, confirmed, cancelled, admin cancelled, proceed for payment (column default waiting, though creation overrides to awaiting confirmation). [src: models/travel_db.model.js:85-93]

  • updateWaitingTravelBooking(booking, t) — on any cancel/admin-cancel of a non-waiting booking, finds the oldest (createdAt ASC) waiting booking on the same date and matching RC-direction and promotes it to awaiting confirmation, then emails the promoted member (rajPravasStatusUpdate). Direction filter only constrains the RC end (pickup=RC or drop=RC), not the specific city. [src: helpers/travelBooking.helper.js:56-97]
  • Because creation never sets waiting, the promotion path is effectively dormant unless a waiting row is introduced elsewhere (legacy data / manual DB edit).

Notifications

Cancel and admin status changes fan out through sendTravelStatusChangeWhatsApp, sendMail (rajPravasStatusUpdate / rajPravasCancellation), and sendDualUserNotifications (push to both the traveler cardno and the bookedBy booker). Unified booking confirmations include a travelBookingDetails slice in sendUnifiedWhatsApp.

Member-facing bus/departure surfacing

Member "Bookings" pulls GET /api/v1/travel/booking (paginated). Each row surfaces the member-facing bus details when the booking has been assigned to a bus: bus_name, departure_time (the timing of the member's own pickup stop, matched by TRIM(LOWER(stop_name)) = TRIM(LOWER(pickup_point))), coordinator_name, and coordinator_contact (the coordinator's issuedto / mobno). Also returns amount, transaction_status, status, admin_comments.

Cancellation

The member cancels via DELETE /api/v1/travel/booking with { bookingid }. Only bookings in awaiting confirmation, confirmed, proceed for payment, or waiting are cancellable. On cancel, userCancelBooking sets the booking's status to cancelled and calls cancelTransaction(user, card, transaction, t, admin=false). That function has an explicit early-exit guard — !admin && [TYPE_TRAVEL, TYPE_UTSAV].includes(getBookingType(transaction)) and the transaction is already paid (completed/cash completed) — which returns { credits: 0 } immediately for a member-initiated travel or utsav cancellation of a paid booking: the transaction is left exactly as it was (still completed) and no credit is issued. (A member cancel of an unpaid pending travel transaction falls through and the transaction is marked cancelled normally.) Room/flat member-cancellations are not in that skip-list, so the same cancelTransaction function does fall through to computing and issuing credit automatically for those — travel/utsav is the deliberate exception, not room/flat. Credit for a travel booking only happens when an admin cancels with an explicit issueCredits: 'yes' (see Admin mechanics, below), which re-runs cancelTransaction with admin=true and skips this guard. A waiting booking on the same date+direction may be auto-promoted to awaiting confirmation (unless the cancelled one was itself waiting), and the member gets a WhatsApp message + email (rajPravasCancellation, cc RAJ_PRAVAS_EMAIL in prod) + push; if the booking was made on behalf of someone, the other party is push-notified too.


Every route

Member realm — /api/v1/travel (middleware validateCard, card-based)

Method Path Auth realm Purpose Key params/body
GET /api/v1/travel/booking Member (card) List the member's travel bookings (as traveler or booker), paginated, with bus + coordinator details query page (def 1), page_size (def 10)
DELETE /api/v1/travel/booking Member (card) Cancel one travel booking; may promote a waiting booking body bookingid
GET /api/v1/travel/events Member (card) checkUpcomingEvents — upcoming events for the travel picker

Member realm — travel creation via unified/mumukshu engine

Method Path Auth realm Purpose Key params/body
POST /api/v1/mumukshu/validate Member (card) Validate travel (XOR, past-date, dup-direction); returns status:'awaiting confirmation', charge:0 primary_booking/addons with booking_type='travel', details.date, details.mumukshuGroup[{ pickup_point, drop_point, luggage, comments, type, mumukshus[], arrival_time, leaving_post_adhyayan, total_people }]
POST /api/v1/mumukshu/booking Member (card) Create travel booking rows (Self or Mumukshu) same shape as validate
GET /api/v1/mumukshu/ Member (card) checkMumukshuOrGuest (booking-mode gate)
POST /api/v1/unified/validate · /api/v1/unified/booking Member (card) Deprecated — both throw HTTP 410

Admin realm — /api/v1/admin/travel (auth + authorizeRoles(travelAdmin, superAdmin, travelAdminDri))

Method Path Purpose Key params/body
GET /upcoming List bookings in a date range with filters, transaction + assigned-bus + stops query start_date, end_date, statuses[], pickupRC, dropRC, adminComments[]
GET /summary Grouped counts by destination (Mumbai↔RC/Other) × status same query filters
GET /driver Confirmed passengers for today (or tomorrow after 8 PM IST), grouped by direction/point none
POST /booking/status Change a booking's status (+ charges/credits/cancel) body bookingid, status, adminComments, description, charges, issueCredits
POST /transaction/status Admin-cancel a booking's transaction body cardno, bookingid, type
PUT /bookingupdate Edit amount / pickup / drop / type / date / bus / coordinator (does not edit leaving_post_adhyayan) body bookingid, amount?, pickup_point?, drop_point?, type?, date?, bus_group_id?, is_coordinator?
POST /bus-group Create a bus (stops + optional auto-assign) body event_date, bus_name, stops[], capacity, notes, force_create, auto_assign, selected_bookingids[]
POST /bus-group/assign-passengers Bulk-assign booking IDs to a bus (no capacity check; rejects any already assigned) body bus_group_id, bookingids[]
GET /bus-group/:id Bus detail: group + passengers (with card) + stops + coordinator param id
PUT /bus-group/coordinator Set the coordinator (passenger must belong to the bus) body bus_group_id, bookingid
GET /bus-groups All buses with passengers + ordered stops none
GET /available-bookings Confirmed/proceed bookings on the date matching the bus direction, not yet assigned query event_date, bus_group_id
DELETE /bus-group/passenger/:bookingid Remove a passenger (clears coordinator if it was them) param bookingid
PUT /bus-group/capacity Update capacity body bus_group_id, capacity
PUT /bus-group/:id Update bus (stops/capacity/notes) + route recalculation body bus_name, stops[], capacity, notes, auto_assign, remove_invalid
POST /bus-group/bulk-assign Validate + assign many bookings to a bus, honoring capacity; optional coordinator body bus_group_id, bookingids[], coordinator_bookingid?
POST /bus/preview-bulk-upload Dry-run of bulk assign (per-row result: Valid/Invalid/Wrong Date/Wrong Route/Already Assigned/Capacity Full) body bus_group_id, bookingids[], rows[], coordinator_bookingid?
GET /bus-group/:id/export Export passenger roster as .xlsx param id
DELETE /bus-group/:id Delete a bus + its stops + passenger links param id
POST /bus-group/preview-create Dry-run create: which date bookings would match the proposed stops body event_date, stops[], capacity
POST /bus-group/preview-update Dry-run update: newly-matching vs no-longer-matching passengers body bus_group_id, stops[], capacity
POST /bulk-master-preview Preview an Excel "master" upload of many buses + assignments body buses[], assignments[], update_existing?
POST /bulk-master-create Commit the master upload (create/update buses, assign passengers) body buses[], update_existing?

Coordinator realm — /api/v1/coordinator (no router-level middleware; dashboard/boarding self-verify a JWT)

Method Path Purpose Key params/body
POST /send-otp Verify caller is an assigned coordinator of an upcoming bus, then WhatsApp a 6-digit OTP body mobno
POST /verify-otp Validate OTP, issue a 7-day JWT { cardno, mobno } body mobno, otp
GET /dashboard Coordinator's upcoming buses: stops, roster, boarded state, remaining seats header Authorization: Bearer <jwt>
PUT /boarding-status Mark a passenger boarded / un-boarded (authz: caller must be that bus's coordinator) header JWT; body passenger_id, boarded

Admin mechanics

Booking review & status (fetchUpcomingBookings, updateBookingStatus, updateTransactionStatus, updateBooking)

  • /upcoming returns each booking joined to its transaction (amount/paymentStatus/paymentDate), the traveler card (name, mobile, center, res_status), and — via travel_bus_passengerstravel_bus_group — the assigned bus (bus_group_id, bus_name, bus_capacity, coordinator_bookingid) plus the bus's ordered stops. If pickup/drop is "Other", the SQL substitutes the free-text comments as the display point.
  • updateBookingStatus is the main status engine. Accepted status values and behavior:
  • proceed for payment — create a pending transaction for charges if none exists; if that transaction is already completed, jump straight to confirmed.
  • confirmed — flip a cash pending or payment pending transaction to completed.
  • admin cancelled — if issueCredits='yes', cancel + issue credits; if 'no' and the transaction is already completed, leave it untouched; otherwise mark the transaction admin-cancelled. On any cancel it also removes the booking from its bus (deletes the passenger row and nulls the bus's coordinator if it was them) and promotes a waiting booking. admin_comments distinguishes reasons: admin_cancel_seats_full → "Cancelled because all seats were booked", admin_cancel_wrong_form → "Cancelled because of wrong form filled", else "Cancelled by admin".
  • seats full cancel / wrong form cancel — cancel the transaction via adminCancelTransaction (see Discrepancies re: the ENUM).
  • Rejects a no-op (status == current), rejects re-cancelling an already-cancelled booking (except cancelledadmin cancelled), and rejects any other status as "Invalid status provided". Always emails + push-notifies both parties + WhatsApp.
  • updateTransactionStatus admin-cancels the transaction of a waiting/confirmed booking.
  • updateBooking (/bookingupdate) edits amount (transaction), and pickup/drop/type/date (travel row) — its travelUpdate object is built only from amount/pickup_point/drop_point/type/date plus bus/coordinator; it does not touch leaving_post_adhyayan. [src: controllers/admin/travelManagement.controller.js:908-913] When route or date changes it revalidates the bus assignment: if the new route/date no longer fits the assigned bus it removes the passenger, optionally re-adds to a supplied bus_group_id (with capacity check → 400 capacityExceeded), and returns a matchingBus suggestion. A manual bus_group_id change is honored even without a route change; is_coordinator ('yes'/'no') sets/clears the coordinator — but only inside the bus_group_id branch (see Discrepancies).

Driver manifest (fetchBookingForDriver)

Returns only confirmed bookings for the fetch date (today before 8 PM IST, else tomorrow), classifying each as "Mumbai to Research Centre" / "Research Centre to Mumbai" via large hard-coded location lists, normalizing the pickup/drop label, using comments for "Other", and ordering by direction then a fixed point priority (Dadar → Amar Mahal → Airoli → others).

Summary (fetchSummary)

Groups counts by computed destination (Mumbai↔RC / Other) × status, with special labels for the two admin-cancel reasons. Honors statuses[], adminComments[], and the pickupRC/dropRC flags.

Bus groups (createBusGroup, updateBusGroup, and previews)

  • A bus = travel_bus_group (event_date, bus_name, capacity, notes, pickup/drop derived from first/last stop, coordinator_bookingid). Its stops = ordered travel_bus_stops (stop_name, stop_order, timing). Its passengers = travel_bus_passengers linking bookingid.
  • There is no "car" vs "bus" concept anywhere in this model. travel_bus_group is the single generic representation of any vehicle — a 2-seater or a 35-seater are both just a bus_name (free text) + capacity (free integer). Nothing in the schema, constants, or controller logic distinguishes an owned car from a hired vehicle; that distinction (e.g. naming a group "Car 1") is purely a staff naming convention, invisible to the system. See Discrepancy #12.
  • Route-drafting and passenger-matching are fused, not sequential. createBusGroup (POST /bus-group) requires ≥2 stops with strictly-ascending timings (normalizeExcelTime parses Excel serials or "HH:MM"); rejects a duplicate (same event_date + bus_name). Its auto_assign flag defaults to true — so in the same transaction as validating stops and creating the bus, it also matches confirmed/proceed for payment bookings on the event date whose pickup and drop both appear in the stop list with pickupIndex < dropIndex, filtered by selected_bookingids and capacity. Over-capacity is a soft gate: it returns capacityExceeded + suggestedCapacity rather than a hard block, and staff can resubmit with force_create to proceed anyway. The admin panel's "preview" (preview-create) is a live, uncommitted, in-form recalculation as staff edit the stop list — not a separately committed step; the same submit action that finalizes the route is what creates the bus and (by default) assigns passengers to it.
  • Update (PUT /bus-group/:id) rewrites stops, recomputes valid passengers, optionally remove_invalid (drop passengers no longer on-route) and auto_assign (fill remaining seats FIFO) — here auto_assign defaults to false, so re-matching on an edit is opt-in.
  • Bulk-assign (POST /bus-group/bulk-assign) and preview-bulk-upload validate each booking (exists / right date / on-route / not already assigned / within remaining capacity) and report per-row outcomes; overflow beyond capacity is skipped (skippedCapacityIds). Bulk-assign can also set coordinator_bookingid in the same request, so coordinator assignment doesn't have to be a separate follow-up call — though the dedicated PUT /bus-group/coordinator endpoint exists for when it is done separately.
  • There are four distinct staff-facing paths onto a vehicle, not one linear sequence: (1) auto-match at create/update time, (2) manual one-by-one via assign-passengers (no capacity check at all — only a duplicate-assignment check), (3) manual bulk via bulk-assign (capacity-checked, optional coordinator), and (4) the Excel bulk-master upload described below, which doesn't route-match at all — staff specify the booking→bus assignments directly in the spreadsheet.
  • Bulk-master (bulk-master-preview / bulk-master-create) ingests an Excel of many buses + assignments: groups rows by Bus Name+Event Date, validates stops against a whitelist (VALID_STOPS, which must include "Research Centre"), checks ascending timings, detects duplicate buses, and (with update_existing) reassigns passengers from an old bus to the new one.
  • Export builds an .xlsx of Name/Mobile/Pickup/Drop/Status/Luggage/Comments/Boarded/Coordinator.

The admin panel exposes these under Travel Management (admin/travel/index.html): Upcoming Bookings, Bookings for Driver, Travel Bus Management (create/edit/bulk-upload modals with preview), Coordinator Login, and Update Booking Status.


The Coordinator role — implementation

A coordinator is a passenger (their own travel_db.bookingid is stored in travel_bus_group.coordinator_bookingid) who is promoted by an admin (setBusCoordinator, bulkAssignPassengersToBus.coordinator_bookingid, or updateBooking.is_coordinator). It is not an admin role — it has its own auth realm and its own OTP table. The role is operationally intended only for buses (see business-logic companion), but nothing in setBusCoordinator/updateBooking/bulkAssignPassengersToBus checks or restricts which travel_bus_group a coordinator can be set on — consistent with Discrepancy #12, the system has no way to know a given travel_bus_group represents a car rather than a bus, so it cannot prevent a coordinator being assigned to one.

  • sendOtp — looks up the card by mobno; requires the card to have a travel booking; fetches all upcoming buses (event_date >= today) with a non-null coordinator, and confirms one of their coordinator_bookingids belongs to this card (else 403 "You are not assigned as coordinator"). Rate-limited to 5 OTPs / 10 min (else 429). Generates a 6-digit OTP, stores it in coordinator_login_otp (expires_at = now + 5 min), and sends it via WhatsApp template coordinator_login_otp (using formatWhatsAppPhone(mobno, country)). [src: controllers/admin/coordinatorAuth.controller.js:41-168]
  • verifyOtp — matches the newest unverified OTP for { mobno, otp }; wrong OTP increments attempts and blocks after 5 attempts (marks it verified, 429); checks expiry; on success marks it verified, resets attempts, and returns a 7-day JWT { cardno, mobno } plus the coordinator's card summary. [src: controllers/admin/coordinatorAuth.controller.js:193-306]
  • fetchCoordinatorDashboard — self-verifies the Bearer JWT, then (same all-buses filter) returns every upcoming bus this card coordinates, each with ordered stops, a passengers list (name, mobno, cardno, pickup point + pickup timing, drop, comments, luggage, boarded, boarded_at), remaining_seats, and totalPassengers. The admin coordinator dashboard page adds a QR reader and search/boarding filters for marking attendance.
  • updateBoardingStatus — self-verifies the JWT, loads the passenger, authorizes that the caller is the coordinator of that passenger's bus (the bus's coordinator_bookingid must map to a travel_db row whose cardno = the JWT's cardno), then sets boarded and boarded_at (now, or null when un-boarding).

Coordinators can therefore see their bus roster and stop timings, and do exactly one write: toggle each passenger's boarding state. They cannot change routes, capacity, charges, or booking status.


Data & relationships

Hub is CardDb (cardno); every travel row hangs off it. All travel tables:

  • travel_db (model TravelDb) — one row per person per leg. bookingid (PK, string/UUID), cardno (FK → card_db, the traveler), bookedBy (FK → card_db, nullable — the booker when acting on behalf; null for self), date (DATEONLY), pickup_point, drop_point, type, luggage, arrival_time (nullable), leaving_post_adhyayan (bool, default 0), total_people (int, nullable), trip_group_id (nullable — links the two legs of a round trip), comments (nullable), admin_comments (nullable), status (ENUM, above), updatedBy (default 'USER'), timestamps. [src: models/travel_db.model.js:15-99 (trip_group_id :69-73)]
  • CardDb.hasMany(TravelDb, {foreignKey:'cardno'}) and CardDb.hasMany(TravelDb, {as:'travelBookedByCard', foreignKey:'bookedBy'}); reciprocals TravelDb.belongsTo(CardDb) on both keys (both CASCADE).
  • travel_bus_group (model TravelBusGroup) — id (UUID PK), event_date (DATEONLY), bus_name, pickup_point/drop_point (nullable, derived from first/last stop), coordinator_bookingid (string, nullable — a loose reference to travel_db.bookingid, not a declared FK), capacity (int), notes (text), createdBy, timestamps.
  • travel_bus_passengers (model TravelBusPassengers) — id (UUID PK), bus_group_id (UUID FK), bookingid (string → travel_db), boarded (bool, default false), boarded_at (date, nullable), timestamps. TravelBusGroup.hasMany(TravelBusPassengers, {as:'passengers'}) (CASCADE); TravelBusPassengers.belongsTo(TravelBusGroup) is aliased twice — as 'busGroup' and as 'TravelBusGroup' (different code paths use different aliases).
  • travel_bus_stops (model TravelBusStops) — id (UUID PK), bus_group_id (UUID), stop_name, stop_order (int), timing (string, nullable). No timestamps. TravelBusGroup.hasMany(TravelBusStops, {as:'stops'}).
  • coordinator_login_otp (model CoordinatorOtp, freezeTableName) — id (UUID PK), mobno, otp (string), expires_at (date), verified (bool, default false), attempts (int, default 0). Not associated to any other model.

Grouping shape: TravelBusGroup hasMany TravelBusPassengers + hasMany TravelBusStops; a passenger row points back to one travel_db booking; the coordinator is one of those bookings referenced by coordinator_bookingid. Cancelling a booking (member or admin) deletes its passenger row and nulls the coordinator pointer if it matched.


Edge cases & branches

  • One-leg-per-direction-per-day: checkTravelAlreadyBooked blocks a second booking in the same direction on the same date; opposite directions on the same date are allowed.
  • Full Car vs Regular: type is free text (client sends "Regular"/"Full Car"); total_people defaults to 1 and is only meaningful for a full car. No backend enum enforcement of type — and, more significantly, type has no effect on vehicle assignment at all (see Discrepancy #12).
  • "Other" location: stored literally as "Other (enter location in comments)"; the driver/upcoming/summary SQL substitutes comments for display. Comments are mandatory client-side only for "Other".
  • Festival date list switch is purely a client concern (the app swaps the picker list); the backend accepts any string in pickup/drop.
  • Driver 8 PM IST cutoff: fetchBookingForDriver shows today's confirmed passengers before 20:00 IST, tomorrow's after.
  • Capacity handling differs by endpoint: assign-passengers does no capacity check (only rejects already-assigned); bulk-assign, preview-bulk-upload, createBusGroup (unless force_create), and updateBooking all enforce capacity and skip/400 overflow.
  • Route validity = stop order: a booking fits a bus only if both its pickup and drop are stops and pickup precedes drop; changing a booking's route/date can silently orphan it from its bus (removedFromOldBus).
  • Coordinator auto-clear: admin cancel, passenger removal, and bulk-master update all null coordinator_bookingid when appropriate.
  • OTP hardening: ≥5 sends/10 min → 429; ≥5 wrong attempts → OTP blocked; 5-minute expiry; newest-first match.
  • fetchUpcoming (member) uses a stop-name join to surface the member's own pickup departure time; if stop names don't match the stored pickup point (casing/spelling), departure_time comes back null.
  • available-bookings empty-array guard: avoids NOT IN (NULL) (which would hide all rows) when no bookings are yet assigned.

Discrepancies

  1. pickupRC/dropRC admin filter is broken. fetchSummary and getAdditionalConditions filter on t1.pickup_point = 'RC' / t1.drop_point = 'RC', but the stored value is always "Research Centre" (RESEARCH_CENTRE). No row equals 'RC', so these filters match zero rows. (/upcoming uses the same buggy 'RC' literal.) [src: controllers/admin/travelManagement.controller.js:101,105,155,158; config/constants.js:18]
  2. Non-ENUM statuses written to an ENUM column. updateBookingStatus has cases for seats full cancel (STATUS_SEATSFULL_CANCELLED) and wrong form cancel (STATUS_WRONGFORM_CANCELLED) and writes them into travel_db.status, but the column ENUM only allows waiting / awaiting confirmation / confirmed / cancelled / admin cancelled / proceed for payment. Writing those pseudo-statuses would be rejected/coerced by MySQL. In practice these cancellations are done as status = 'admin cancelled' + admin_comments = 'admin_cancel_seats_full' | 'admin_cancel_wrong_form' (which the summary SQL relabels) — so the two dedicated cases look like dead/legacy branches. [src: controllers/admin/travelManagement.controller.js:622,628,669; models/travel_db.model.js:85-93; config/constants.js:48-49]
  3. XOR enforced inconsistently. The RC XOR pairing is enforced client-side and in checkTravelAvailability (validate) but not re-checked in bookTravelForMumukshus (the actual create). A direct POST /mumukshu/booking with both endpoints non-RC (or both RC) would slip through; checkTravelAlreadyBooked also assumes exactly one side is RC. [src: helpers/travelBooking.helper.js:132-202 (no XOR re-check)]
  4. Waitlist logic is effectively dormant. Creation always produces awaiting confirmation and returns waitingBookingCount: 0; nothing in the current code path sets waiting for travel, yet updateWaitingTravelBooking (and the column default waiting) exist to promote waiting rows. Direction promotion also only matches on the RC end + date + FIFO, ignoring the specific city — a waiting leg to a different city could be promoted. [src: helpers/travelBooking.helper.js:66-83,182,201]
  5. is_coordinator gated on bus_group_id. In updateBooking, the coordinator set/clear block is nested inside the if (bus_group_id !== undefined) branch, so you cannot toggle coordinator status without also passing bus_group_id. [src: controllers/admin/travelManagement.controller.js:1124-1261 (coordinator block :1217-1260)]
  6. No travel pricing model. Unlike room/food, travel has no price constant and creates no transaction; every charge is admin-entered later. Member "Travel Charge" therefore only appears once an admin sets it. [src: helpers/travelBooking.helper.js:277 (charge:0, no transaction)]
  7. coordinator_bookingid is an un-declared FK — a plain string column pointing at travel_db.bookingid with no association/constraint, so integrity relies entirely on the cancel/remove handlers nulling it. [src: models/associations.js:368-401 (no coordinator_bookingid association)]
  8. Coordinator auth loads all buses. sendOtp and fetchCoordinatorDashboard fetch every upcoming bus with a coordinator system-wide, then filter in app code — correct but not scoped to the requester. [src: controllers/admin/coordinatorAuth.controller.js:72-87,394-409]
  9. Duplicated belongsTo alias. TravelBusPassengers.belongsTo(TravelBusGroup) is declared as both 'busGroup' and 'TravelBusGroup'; different controllers rely on different aliases. [src: models/associations.js:376-386]
  10. ~~Guest travel exists in wiring but never as a flow.~~ CORRECTED (was false). Guest travel creation is fully implemented and live end-to-end. The guest booking controller dispatches case TYPE_TRAVEL → bookTravelGuest (normalizes the guest group and calls bookTravelForMumukshus/bookRoundTripTravel), with validate wired alongside — and the member app surfaces a Guest chip in the travel flow. The old claim that there was "no guest travel path" reflected an earlier version of the code. [src: controllers/client/guestBooking.controller.js:423-424,494-495,546-559; aashray-app/src/components/booking/TravelBooking.tsx:22,924-970]
  11. No system representation of the fleet/threshold/confirmation-timing decision layer. Nothing in the backend tracks how many bookings exist for a given date+direction, flags a route approaching its confirm-by-T-24h target, or distinguishes "cancelled — insufficient headcount" as a reason. The two existing admin-comment codes (admin_cancel_seats_full, admin_cancel_wrong_form) don't cover this very common real-world cancellation reason — today an admin doing this would just use a generic admin cancelled status with a free-text comment. This decision layer is described in plain terms in the business-logic companion's "Deciding whether — and how — to run a trip" section.
  12. "Full Car" bookings are not connected to the centre's owned cars in any way. createBusGroup, updateBusGroup, assignPassengersToBus, bulkAssignPassengersToBus, and previewBulkUpload (all in travelManagement.controller.js) match and filter bookings purely by date, status, and route (pickup/drop stop order) — none of them reference booking.type. The TRAVEL_TYPE_FULL constant is defined in config/constants.js but never imported or used anywhere else in the codebase — it's dead code. [src: config/constants.js:79 (only reference); controllers/admin/travelManagement.controller.js (bus fns have no booking.type ref)] Combined with finding above (no car/bus distinction in travel_bus_group at all), this means a member who requests "Full Car" has that recorded only as a label on their booking; nothing in the system connects it to actually being assigned the owned car specifically — that link, if it happens, is made entirely in a staff member's head when they manually assign passengers to a bus group.

How this connects to other domains (technical)

  • Booking Lifecycle & Engine (business-logic file 03): travel is created only through the unified/mumukshu engine (POST /mumukshu/booking, booking_type='travel'), shares the bookedBy-vs-cardno on-behalf model, the awaiting confirmationconfirmed/proceed for payment status arc, and the credit/refund paths (userCancelBooking, adminCancelTransaction, cancelTransaction).
  • Payments, Pricing & Credits (business-logic file 08): travel has no auto price; charges are set by admins (proceed for payment + charges, or bookingupdate.amount), settled through the shared transactions table and Razorpay, and can be refunded as credits (issueCredits).
  • Adhyayan & Utsav (business-logic file 07): the festival-date location list switch and the self-reported leaving_post_adhyayan flag tie travel to event/adhyayan schedules; the planned adhyayan cross-check (business-logic Planned changes #4) would read ShibirBookingDb/ShibirDb start/end dates to replace it.
  • Admin Panel Map (technical-only file 11, no business-logic pairing): the travelAdmin/travelAdminDri/superAdmin roles, the Travel Management pages, and the separate coordinator sub-app are catalogued there.
  • Status & Transaction System (business-logic file 13): travel reuses the global status constants and the transactions category travel.
  • Accounts, Identity & Auth (business-logic file 02): the planned family/household reminder logic needs a data-model addition that doesn't exist yet — no table anywhere links two member cards together as a family unit; GuestRelationship only covers member↔guest.