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.mdon 2026-07-07 againstaashray-backendmain. 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_adhyayanis not set at creation nor editable viaupdateBooking;updatedByat creation is the cardno; the model has atrip_group_idcolumn; aGET /travel/eventsroute 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/validate → validateBooking → checkTravelAvailability(data) for booking_type = 'travel'.
- POST /api/v1/mumukshu/booking → mumukshuBooking → dispatch case TYPE_TRAVEL → bookTravel → 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]
checkTravelAlreadyBooked — helpers/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-runscheckTravelAlreadyBookedper group. - For each mumukshu in each group, inserts a
travel_dbrow withbookingid = uuidv4(),cardno = mumukshu,bookedBy = (booker if booker ≠ mumukshu else null),status = 'awaiting confirmation', plusdate, type, pickup_point, drop_point, luggage, arrival_time, total_people (default 1), comments, andupdatedBy = user.cardno(not the literal'USER'). Note:leaving_post_adhyayanis 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 isawaiting confirmationand 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)waitingbooking on the samedateand matching RC-direction and promotes it toawaiting 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 awaitingrow 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)¶
/upcomingreturns each booking joined to its transaction (amount/paymentStatus/paymentDate), the traveler card (name, mobile, center, res_status), and — viatravel_bus_passengers→travel_bus_group— the assigned bus (bus_group_id,bus_name,bus_capacity,coordinator_bookingid) plus the bus's orderedstops. If pickup/drop is "Other", the SQL substitutes the free-textcommentsas the display point.updateBookingStatusis the main status engine. Acceptedstatusvalues and behavior:proceed for payment— create a pending transaction forchargesif none exists; if that transaction is already completed, jump straight toconfirmed.confirmed— flip acash pendingorpayment pendingtransaction tocompleted.admin cancelled— ifissueCredits='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_commentsdistinguishes 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 viaadminCancelTransaction(see Discrepancies re: the ENUM).- Rejects a no-op (
status == current), rejects re-cancelling an already-cancelled booking (exceptcancelled→admin cancelled), and rejects any other status as "Invalid status provided". Always emails + push-notifies both parties + WhatsApp. updateTransactionStatusadmin-cancels the transaction of awaiting/confirmedbooking.updateBooking(/bookingupdate) edits amount (transaction), and pickup/drop/type/date (travel row) — itstravelUpdateobject is built only from amount/pickup_point/drop_point/type/date plus bus/coordinator; it does not touchleaving_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 suppliedbus_group_id(with capacity check → 400capacityExceeded), and returns amatchingBussuggestion. A manualbus_group_idchange is honored even without a route change;is_coordinator('yes'/'no') sets/clears the coordinator — but only inside thebus_group_idbranch (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 = orderedtravel_bus_stops(stop_name, stop_order, timing). Its passengers =travel_bus_passengerslinkingbookingid. - There is no "car" vs "bus" concept anywhere in this model.
travel_bus_groupis the single generic representation of any vehicle — a 2-seater or a 35-seater are both just abus_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 (normalizeExcelTimeparses Excel serials or "HH:MM"); rejects a duplicate (same event_date + bus_name). Itsauto_assignflag defaults totrue— so in the same transaction as validating stops and creating the bus, it also matchesconfirmed/proceed for paymentbookings on the event date whose pickup and drop both appear in the stop list withpickupIndex < dropIndex, filtered byselected_bookingidsand capacity. Over-capacity is a soft gate: it returnscapacityExceeded+suggestedCapacityrather than a hard block, and staff can resubmit withforce_createto 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, optionallyremove_invalid(drop passengers no longer on-route) andauto_assign(fill remaining seats FIFO) — hereauto_assigndefaults tofalse, 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 setcoordinator_bookingidin the same request, so coordinator assignment doesn't have to be a separate follow-up call — though the dedicatedPUT /bus-group/coordinatorendpoint 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 viabulk-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 byBus Name+Event Date, validates stops against a whitelist (VALID_STOPS, which must include "Research Centre"), checks ascending timings, detects duplicate buses, and (withupdate_existing) reassigns passengers from an old bus to the new one. - Export builds an
.xlsxof 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 bymobno; requires the card to have a travel booking; fetches all upcoming buses (event_date >= today) with a non-null coordinator, and confirms one of theircoordinator_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 incoordinator_login_otp(expires_at = now + 5 min), and sends it via WhatsApp templatecoordinator_login_otp(usingformatWhatsAppPhone(mobno, country)). [src: controllers/admin/coordinatorAuth.controller.js:41-168]verifyOtp— matches the newest unverified OTP for{ mobno, otp }; wrong OTP incrementsattemptsand 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 orderedstops, apassengerslist (name, mobno, cardno, pickup point + pickup timing, drop, comments, luggage, boarded, boarded_at),remaining_seats, andtotalPassengers. 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'scoordinator_bookingidmust map to atravel_dbrow whosecardno= the JWT's cardno), then setsboardedandboarded_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(modelTravelDb) — 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'})andCardDb.hasMany(TravelDb, {as:'travelBookedByCard', foreignKey:'bookedBy'}); reciprocalsTravelDb.belongsTo(CardDb)on both keys (bothCASCADE).travel_bus_group(modelTravelBusGroup) —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 totravel_db.bookingid, not a declared FK),capacity(int),notes(text),createdBy, timestamps.travel_bus_passengers(modelTravelBusPassengers) —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(modelTravelBusStops) —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(modelCoordinatorOtp,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:
checkTravelAlreadyBookedblocks a second booking in the same direction on the same date; opposite directions on the same date are allowed. - Full Car vs Regular:
typeis free text (client sends "Regular"/"Full Car");total_peopledefaults to 1 and is only meaningful for a full car. No backend enum enforcement oftype— and, more significantly,typehas 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
commentsfor 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:
fetchBookingForDrivershows today's confirmed passengers before 20:00 IST, tomorrow's after. - Capacity handling differs by endpoint:
assign-passengersdoes no capacity check (only rejects already-assigned);bulk-assign,preview-bulk-upload,createBusGroup(unlessforce_create), andupdateBookingall enforce capacity and skip/400overflow. - 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-masterupdate all nullcoordinator_bookingidwhen 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_timecomes back null.available-bookingsempty-array guard: avoidsNOT IN (NULL)(which would hide all rows) when no bookings are yet assigned.
Discrepancies¶
pickupRC/dropRCadmin filter is broken.fetchSummaryandgetAdditionalConditionsfilter ont1.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. (/upcominguses the same buggy'RC'literal.)[src: controllers/admin/travelManagement.controller.js:101,105,155,158; config/constants.js:18]- Non-ENUM statuses written to an ENUM column.
updateBookingStatushascases forseats full cancel(STATUS_SEATSFULL_CANCELLED) andwrong form cancel(STATUS_WRONGFORM_CANCELLED) and writes them intotravel_db.status, but the column ENUM only allowswaiting / 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 asstatus = '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] - XOR enforced inconsistently. The RC XOR pairing is enforced client-side and in
checkTravelAvailability(validate) but not re-checked inbookTravelForMumukshus(the actual create). A directPOST /mumukshu/bookingwith both endpoints non-RC (or both RC) would slip through;checkTravelAlreadyBookedalso assumes exactly one side is RC.[src: helpers/travelBooking.helper.js:132-202 (no XOR re-check)] - Waitlist logic is effectively dormant. Creation always produces
awaiting confirmationand returnswaitingBookingCount: 0; nothing in the current code path setswaitingfor travel, yetupdateWaitingTravelBooking(and the column defaultwaiting) exist to promotewaitingrows. 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] is_coordinatorgated onbus_group_id. InupdateBooking, the coordinator set/clear block is nested inside theif (bus_group_id !== undefined)branch, so you cannot toggle coordinator status without also passingbus_group_id.[src: controllers/admin/travelManagement.controller.js:1124-1261 (coordinator block :1217-1260)]- 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)] coordinator_bookingidis an un-declared FK — a plain string column pointing attravel_db.bookingidwith no association/constraint, so integrity relies entirely on the cancel/remove handlers nulling it.[src: models/associations.js:368-401 (no coordinator_bookingid association)]- Coordinator auth loads all buses.
sendOtpandfetchCoordinatorDashboardfetch 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] - Duplicated
belongsToalias.TravelBusPassengers.belongsTo(TravelBusGroup)is declared as both'busGroup'and'TravelBusGroup'; different controllers rely on different aliases.[src: models/associations.js:376-386] - ~~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 callsbookTravelForMumukshus/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] - 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 genericadmin cancelledstatus 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. - "Full Car" bookings are not connected to the centre's owned cars in any way.
createBusGroup,updateBusGroup,assignPassengersToBus,bulkAssignPassengersToBus, andpreviewBulkUpload(all intravelManagement.controller.js) match and filter bookings purely bydate,status, and route (pickup/drop stop order) — none of them referencebooking.type. TheTRAVEL_TYPE_FULLconstant is defined inconfig/constants.jsbut 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 intravel_bus_groupat 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 thebookedBy-vs-cardnoon-behalf model, theawaiting confirmation→confirmed/proceed for paymentstatus 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, orbookingupdate.amount), settled through the sharedtransactionstable 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_adhyayanflag tie travel to event/adhyayan schedules; the planned adhyayan cross-check (business-logic Planned changes #4) would readShibirBookingDb/ShibirDbstart/end dates to replace it. - Admin Panel Map (technical-only file 11, no business-logic pairing): the
travelAdmin/travelAdminDri/superAdminroles, 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
transactionscategorytravel. - 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;
GuestRelationshiponly covers member↔guest.