Food (Raj Prasad) — Technical Reference¶
Part of the Aashray Technical Reference. Pairs with Business Logic — Food, which describes these rules in plain terms with no code detail. Related technical detail currently lives inline in: Booking Lifecycle, Stay, Rooms & Flats, Adhyayan & Utsav, Payments, Admin Panel Map (not yet split out into its own technical file).
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 pre-ordering meals (breakfast / lunch / dinner) with spice and hightea preferences, the free-for-members / paid-for-guests pricing model, the two booking cutoffs (11 AM previous-day to book, 8 PM previous-day to cancel), bulk food booking for guest/department groups, physical plate issuing (both the per-card food-scanner flow and the K1-kitchen plate counter), menu publishing, all admin reports, and the three nightly WhatsApp meal-count cron jobs that tell the kitchen how many plates to prepare for tomorrow.
Two backing tables carry meals: food_db — one row per (cardno, date), each row a boolean triple (breakfast / lunch / dinner) for an individual person; and bulk_food_booking — one row per (department, date) carrying integer counts for anonymous guest groups. Both hang off the CardDb hub via cardno (and food_db additionally via bookedBy = the payer). Meal reports and the kitchen counts merge both tables plus food_physical_plate (the manual K1 counter).
Verification status: audited & anchored per
docs/DOCS-METHODOLOGY.mdon 2026-07-07 againstaashray-backendmain(with theaashray-adminpanel cross-checked). Every rule carries a[src: …]anchor. Correction this pass: a previously-listed discrepancy claiming the admin "Meal Count" page is broken was found to be false — the page keys on the HTTPresponse.ok, not a missingsuccessfield, and works correctly — so it has been removed. All other discrepancies confirmed real.
Feature 1 — Member food booking (meals · spice · hightea · date range)¶
What it is¶
A member pre-orders meals for themselves, a guest, or a group of mumukshus over a date (or date range). Each person-day is a single food_db row with three meal booleans, a spicy flag, and a hightea choice.
Member-app behavior¶
- Entry: Book Now → Raj Prasad chip. Self food books instantly — no add-ons screen, no review. Guest/Mumukshu food is an add-on inside their respective booking carts and goes through the Review screen.
- Fields (Self): Food Type (multi-select Breakfast/Lunch/Dinner, all three on by default), Spice Level (Regular default / Non Spicy), Hightea (Tea / Coffee / None default for Self; Tea default when it is a guest/mumukshu food add-on).
- A warning callout: "Bookings must be made before 11 AM of the previous day for upcoming meals." The calendar's minimum selectable date is shifted by this rule: before 11 AM → earliest = tomorrow; 11 AM or later → earliest = day-after-tomorrow. End day optional (defaults to start day).
- Success alert "Booking Successful"; server errors show as a modal with the raw message + "Okay".
Backend rules & logic¶
Creation does not go through foodBooking.controller.js — it flows through the booking engine (mumukshuBooking / guestBooking controllers → bookFood → bookFoodForMumukshus in helpers/foodBooking.helper.js). [src: controllers/client/mumukshuBooking.controller.js:535-537; controllers/client/guestBooking.controller.js:591-593; helpers/foodBooking.helper.js:81] Key logic:
validateFood(per card): the meal may only be booked if the card'sres_statusis one ofPR/SEVA KUTIR/GUEST, or the caller hasfoodAdmin/superAdminrole, or the person has a room/flat booked (in-progress cart, already-booked room, already-booked flat, or a special allowance) on the dates. Otherwise →400 ERR_ROOM_MUST_BE_BOOKED= "Must have room booked on one or more selected dates." In practice this forces plain MUMUKSHU-status users to have a stay before they can order food; residents/seva-kutir/guests are exempt. [src: helpers/foodBooking.helper.js:323-357; config/constants.js:126-127]- One row per (cardno, date). If a row exists it is OR-merged:
breakfast: booking.breakfast || breakfast(a meal can be turned on by a new booking but this path never turns one off), andhightea,spicy,updatedByare overwritten. Otherwise a new row isbulkCreated withid = uuidv4(),plateissued: 0. [src: helpers/foodBooking.helper.js:161-184,214] bookedByis set to the payer's cardno when booking for someone else,nullwhen booking for self (bookedBy !== mumukshu ? bookedBy : null). [src: helpers/foodBooking.helper.js:178]- Pricing / charging: only cards with
res_status == GUESTare charged. For each newly-added meal (not one that already existed) aTransactionsrow is created withcategory=breakfast/lunch/dinner,amount= the per-meal price,cardno= the payer (bookedBy),status=cash pending(ifcashAllowed, i.e. admin) orpending(app). Residents & mumukshus are never charged for food. [src: helpers/foodBooking.helper.js:191-208] - Prices (hardcoded in
config/constants.js):BREAKFAST_PRICE = 60,LUNCH_PRICE = 120,DINNER_PRICE = 120(INR). [src: config/constants.js:22-24] - Utsav interplay (
getDatesDuringUtsav): if the food is attached to a Utsav primary booking, food is created only for dates outside the utsav window (days beforestart_dateand days afterend_date) — meals during the festival are part of the utsav package, so only the early-arrival / late-departure days get individual food rows. [src: helpers/foodBooking.helper.js:300-321] - Display-only meal windows returned by the client menu endpoint: breakfast 7:45–8:45 AM, lunch 12:00–1:00 PM, dinner 5:45–6:45 PM.
Every route¶
Food creation is part of the booking engine; the food-specific client namespace (/api/v1/food, all guarded by validateCard) only reads and cancels.
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| POST | /api/v1/mumukshu/booking |
Member (validateCard) | Create food for Self or Mumukshus (booking_type: food) |
primary_booking/addons with meals, spicy, high_tea, start/end dates |
| POST | /api/v1/mumukshu/validate |
Member | Price/availability preview for mumukshu food | same shape; returns foodDetails charge + usable credits |
| POST | /api/v1/guest/booking |
Member | Create food for Guests (booking_type: food) |
guest groups + meals |
| POST | /api/v1/guest/validate |
Member | Guest food price preview (guests are charged) | returns charge + availableCredits |
| GET | /api/v1/food/get |
Member | List the caller's upcoming meals (unpivoted to one row per meal) | date, meal(csv), spice(true/false/all), bookedFor(all/self/<cardno>), page, page_size (default 15) |
| GET | /api/v1/food/getGuestsForFilter |
Member | Dropdown of people the caller has booked food for | — (returns All, Self, then each guest cardno→name) |
| GET | /api/v1/food/menu |
Member | Upcoming published menu keyed by date | — |
| PATCH | /api/v1/food/cancel |
Member | Cancel meals (see Feature 3) | cardno, food_data[] |
Admin operations¶
Admin can book food on any card via POST /api/v1/admin/food/book (bookFood) — validates the card by cardno or mobno, builds a single-person group via createGroupFoodRequest, calls bookFoodForMumukshus with cashAllowed = true and the admin's roles (so validateFood's room requirement is bypassed for food admins). After commit it fires a food-confirmation WhatsApp (sendUnifiedWhatsApp → sendFoodWhatsApp).
Data & relationships¶
food_db: id (STRING uuid, PK), cardno (FK → card_db.cardno), bookedBy (nullable FK → card_db.cardno), date (DATEONLY), breakfast/lunch/dinner (BOOLEAN), *_plate_issued (BOOLEAN default 0), hightea (ENUM TEA/COFFEE/NONE, default NONE), spicy (BOOLEAN), updatedBy (default USER). [src: models/food_db.model.js:7-73] Associations: CardDb hasMany FoodDb on both cardno and bookedBy (CASCADE); FoodDb belongsTo CardDb (cardno) and (bookedBy as bookedByCard). Transactions link to a meal via Transactions.bookingid = food_db.id and category = the meal name.
Edge cases & branches¶
bookFoodForMumukshusgroups accept eithermumukshusorguestsarrays (group.mumukshus || group.guests), so the same helper serves member and guest carts.- A meal already booked is not re-charged (checked against
existingMeals[meal.type]). FetchFoodBookingsbuilds threeUNION ALLsub-selects (one per meal) then filters —bookedFor=selfmaps tobookedBy IS NULL; a numericbookedFormaps tocardno = <that guest>. Ordered by date, then breakfast→lunch→dinner.- There is no DB unique constraint on (
cardno,date) — the "one row per person-day" invariant is enforced only by applicationfindOnelogic. bookFoodForAllMeals/cancelAllMealshelpers exist for utsav-package food (auto-book all meals across a stay, honoringstarting_meal/ending_mealon the first/last day, forcingspicy=1,hightea='TEA').
Feature 2 — Food pricing¶
What it is¶
Flat per-meal prices, charged only to guests.
Backend rules & logic¶
- Prices are constants in
config/constants.js: breakfast ₹60, lunch ₹120, dinner ₹120. There is afoodratetable +FoodRatemodel (mealtype,rate,updatedBy) but no code anywhere reads or writes it — pricing is entirely hardcoded (see Discrepancies). - Members (PR / MUMUKSHU / SEVA KUTIR) pay nothing for food. Guests pay per new meal, billed to the booker's card, integrated into the booking cart total and settled via the normal payment/credits flow (see Payments).
checkFoodAvailabilityForMumumkshuscomputes the preview charge: for guest bookings it clones the user's credits, sums the charge for each new (not-already-booked) meal, and returnsusableCredits(tempUser, TYPE_FOOD, charge).
Admin operations¶
None — there is no admin screen or endpoint to edit food rates (the foodrate table is orphaned).
Feature 3 — Cancellation (8 PM day-before cutoff)¶
What it is¶
A member (or admin) cancels one or more previously-booked meals; guest meals also reverse their transaction.
Member-app behavior¶
Cancel from the bookings list; each cancel targets a specific (date, mealType, bookedFor). Server rejects late cancels with "Bookings can only be cancelled up to 8:00 PM of the previous day."
Backend rules & logic (cancelFood)¶
- Timezone
Asia/Kolkata. Member cutoff: for each item the cutoff =date − 1 dayat 20:00 IST; the cancel is allowed only if now ≤ cutoff. Items past cutoff are silently filtered out; iffood_datawas non-empty but all items were filtered →400with the 8 PM message. [src: helpers/foodBooking.helper.js:398-423 (cutoff :409-415)] - Admin cutoff (
admin = true):validDate = today; any item withdate >= todayis allowed (no 8 PM restriction). [src: helpers/foodBooking.helper.js:402-406] - For each valid item it finds the
food_dbrow by (bookedFor || cardno,date), callscancelMeal(sets that one meal boolean to0, stampsupdatedBy). If the meal was a guest meal (bookedForpresent) it locates the matchingTransactionsrow (bybookingid+category = mealTypeMapping[mealType]) and appends it forcancelTransactions(which refunds/credits per the payments rules). - A
// FIXME: guests can book self meals toonote flags that transaction reversal only triggers whenbookedForis set.
Admin operations¶
- PUT
/api/v1/admin/food/cancel/:bookingid?mealType=(cancelBooking) — cancels one meal on onefood_dbrow (looked up byid+ that meal being true), reverses any linked transaction viaadminCancelTransaction. No date cutoff. - PUT
/api/v1/admin/food/cancel_multiple(cancelMultipleMeals) — bodymeals: [{bookingid, mealType}]; loops the same logic in one transaction, skipping rows where the meal isn't set. - Cron auto-cancel: the 30-minute payment-timeout cron (
cron.js) also cancels food meals for expired unpaid transactions viacancelMeals→cancelMeal(food is handled specially — the transaction is cancelled but the whole booking isn't reopened like rooms/adhyayan).
Every route¶
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| PATCH | /api/v1/food/cancel |
Member | Bulk-cancel own/guest meals (8 PM rule) | cardno, food_data: [{date, mealType, bookedFor}] |
| PUT | /api/v1/admin/food/cancel/:bookingid |
superAdmin/foodAdmin/smilesAdmin | Cancel one meal + reverse txn | ?mealType=breakfast\|lunch\|dinner |
| PUT | /api/v1/admin/food/cancel_multiple |
superAdmin/foodAdmin/smilesAdmin | Cancel many meals | meals: [{bookingid, mealType}] |
Edge cases & branches¶
cancelMealonly zeroes the targeted meal; the row (and other meals on it) stays.- On the member path, mixing cancellable and past-cutoff items succeeds for the cancellable ones and simply drops the rest — no partial-failure message.
Feature 4 — Bulk food booking (guest / department groups)¶
What it is¶
A count-based booking for anonymous groups — departments (RC, SREC, SRMC, Smilestones, Sanisa, Events-Guest, Personal) or event guests. One bulk_food_booking row = a (card, date) with meal counts rather than per-person rows.
Backend rules & logic¶
bulkBooking(POST/bulk_booking): requirescardnoormobno; resolves theCardDb; creates aBulkFoodBookingwithbreakfast/lunch/dinner = guestCount if selected else 0, plate-issued counts 0, chosendepartment,updatedBy. No charge is computed (bulk food is not billed through Transactions). Sends abulk_food_booking_confirmedWhatsApp to the card's phone.- smilesAdmin time restriction: if the caller has role
smilesAdminanddateis tomorrow and now is after 11:00 AM today →403 "You can't book food for tomorrow after 11:00 AM today." editBulkBooking(PUT/edit_bulk_booking/:bookingid): updates the three meal counts;guestCountis recomputed asmax(breakfast, lunch, dinner, guestCount)(derived, not authoritative). smilesAdmin edit cutoff is 8:00 PM for a tomorrow booking (note: create cutoff is 11 AM, edit cutoff is 8 PM — inconsistent). Sendsbulk_food_booking_updatedWhatsApp.
Admin operations¶
Admin panel: Manage Guest Food Bookings (manageBulkFood.html/.js). Create form (card/mobile, date, guest count, meal checkboxes, department). For smilesAdmin the department dropdown is locked to Smilestones and the plate-issued columns are hidden. Existing bookings render with ± steppers that debounce (2.5 s) into edit_bulk_booking; plate-issued steppers call update_plate_issued.
Every route¶
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| POST | /api/v1/admin/food/bulk_booking |
superAdmin/foodAdmin/smilesAdmin | Create a bulk (department) booking | cardno|mobno, date, guestCount, breakfast/lunch/dinner, department |
| GET | /api/v1/admin/food/bulk_booking |
superAdmin/foodAdmin/smilesAdmin | List upcoming bulk bookings (joined to CardDb) | cardno|mobno |
| PUT | /api/v1/admin/food/edit_bulk_booking/:bookingid |
superAdmin/foodAdmin/smilesAdmin | Edit meal counts | breakfast, lunch, dinner, guestCount |
Data & relationships¶
bulk_food_booking: bookingid (STRING uuid PK), cardno (FK → card_db), date (DATEONLY), guestCount (INT), breakfast/lunch/dinner (INT counts), *_plate_issued (INT default 0), department (STRING), updatedBy. CardDb hasMany BulkFoodBooking; BulkFoodBooking belongsTo CardDb. [src: models/bulk_food_booking.model.js]
Admin food route gating [src: routes/admin/foodManagement.routes.js:34,41]: the
/physicalPlatesgroup issuperAdmin/foodAdmin/foodPlateAdmin; every other admin food route (issue, book, cancel, bulk booking, plate count, reports, menu, meal-count) issuperAdmin/foodAdmin/smilesAdmin.
Feature 5 — Physical plate issuing & food scanner¶
Two independent plate-tracking mechanisms exist: per-card individual plates (on food_db, one boolean per meal) and kitchen-level physical plate counts (food_physical_plate). Bulk bookings track plate issuance as integer counts on bulk_food_booking.
5a. Individual plate issue (member + guest plates via card/QR)¶
What it is: at meal time, staff scan a member's card (or type the cardno) to mark that meal's plate as served.
Backend logic (issueFoodPlate):
- Resolves targetDate (provided date, or today in IST); finds the food_db row for (cardno, targetDate). No row → 404 ERR_BOOKING_NOT_FOUND; unknown card → 404.
- If no meal is passed, auto-detects by current IST time using end-windows: breakfast ≤ 10:00, lunch ≤ 14:00, dinner ≤ 19:00. After 19:00 nothing matches → 400 ERR_INVALID_MEAL_TIME. [src: helpers/foodBooking.helper.js:644-694 (windows :652-654)]
- Validates the meal was booked (booking[currentMeal] truthy) else 400 "<meal> not booked"; if <meal>_plate_issued already true → 400 "Plate for <meal> already issued". [src: helpers/foodBooking.helper.js:696-704]
- Sets <meal>_plate_issued = true; returns { message, issuedto }.
Admin operations:
- Issue Plate (issuePlate.html/.js): manual cardno entry → POST /food/issue/:cardno. Error styling differentiates already issued (warning/brown), invalid meal time (info/blue), booking not found (danger/red); plays an error sound.
- Issue Plate – Scanner (issuePlateScan.js): Html5Qrcode camera scanner; decodes cardnumber=XXXX payloads, strips the prefix, calls the same endpoint, with a 1.5 s scan lock between reads.
- Bulk issue (POST /food/issue/bulk): {cardnos[], meal, date} — loops issueFoodPlate(cardno, meal, t, date) for a group; whole batch rolls back on any error.
5b. Bulk-booking plate issuance¶
updatePlateIssued (PUT /update_plate_issued/:bookingid): sets <mealType>_plate_issued to a count. Guards: valid meal type; booking must be for today (403 "Plates can only be issued for today's bookings."); count may not exceed the booked count (400 "Cannot issue more than <booked> plates").
5c. K1 kitchen physical plate counter¶
What it is: a manual per-day, per-meal tally of physical plates the kitchen actually put out (independent of who was scanned), used in reports to reconcile against booked/issued.
Backend logic:
- physicalPlatesIssued (POST): one row per (date, type); 400 if a row for that (date, type) already exists (must use PUT to change).
- updatePhysicalPlate (PUT): updates count; 404 if none exists.
- fetchPhysicalPlateIssued (GET): list, optional ?date=.
Admin operations: the Plates Issued (plateCount) screen. This group of endpoints is the only one also open to the foodPlateAdmin role.
Every route¶
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| POST | /api/v1/admin/food/issue/:cardno |
superAdmin/foodAdmin/smilesAdmin | Issue one individual plate (auto/explicit meal) | body {meal?} |
| POST | /api/v1/admin/food/issue/bulk |
superAdmin/foodAdmin/smilesAdmin | Issue plates for many cards | {cardnos[], meal, date} |
| PUT | /api/v1/admin/food/update_plate_issued/:bookingid |
superAdmin/foodAdmin/smilesAdmin | Set bulk-booking plate count | {mealType, plateIssued} |
| POST | /api/v1/admin/food/physicalPlates |
superAdmin/foodAdmin/foodPlateAdmin | Add K1 kitchen plate count | {date, type, count} |
| GET | /api/v1/admin/food/physicalPlates |
superAdmin/foodAdmin/foodPlateAdmin | Fetch kitchen plate counts | ?date= |
| PUT | /api/v1/admin/food/physicalPlates |
superAdmin/foodAdmin/foodPlateAdmin | Update kitchen plate count | {date, type, count} |
Data & relationships¶
food_physical_plate: composite PK (date DATEONLY, type ENUM breakfast/lunch/dinner), count (INT), updatedBy.
Feature 6 — Menu management¶
What it is¶
Publishing what dish is served each meal each date; members see the upcoming menu.
Member-app behavior¶
GET /api/v1/food/menu returns upcoming dates keyed by date, each an array of {meal, name, time} with the fixed display times (breakfast 7:45–8:45 AM, lunch 12:00–1:00 PM, dinner 5:45–6:45 PM). Empty → { data: null }.
Backend rules & logic¶
Menu rows are unique per date. Admin addMenu rejects an existing date (400); updateMenu requires an existing date (404); deleteMenu returns 404 if nothing deleted; addBulkMenu upserts (updateOnDuplicate: [breakfast, lunch, dinner, updatedAt]) after filtering to records with all three meal fields present.
Admin operations¶
Menu Management screen (fetchMenu / addMenu / updateMenu). Admin fetchMenu returns a flat array for a [startDate, endDate] range (different shape from the member endpoint's date-keyed object).
Every route¶
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| GET | /api/v1/food/menu |
Member | Upcoming menu (date-keyed) | — |
| GET | /api/v1/admin/food/menu |
superAdmin/foodAdmin/smilesAdmin | Menu in range (array) | ?startDate=&endDate= |
| POST | /api/v1/admin/food/menu |
superAdmin/foodAdmin/smilesAdmin | Add one day's menu | {date, breakfast, lunch, dinner} |
| PUT | /api/v1/admin/food/menu |
superAdmin/foodAdmin/smilesAdmin | Update a day's menu | {date, breakfast, lunch, dinner} |
| DELETE | /api/v1/admin/food/menu |
superAdmin/foodAdmin/smilesAdmin | Delete a day's menu | ?date= |
| POST | /api/v1/admin/food/menu/bulk |
superAdmin/foodAdmin/smilesAdmin | Bulk upsert menus | {menus: [{date, breakfast, lunch, dinner}]} |
Data & relationships¶
menu: id (INT autoincrement PK), date (DATEONLY unique), breakfast/lunch/dinner (STRING dish names), updatedBy. Menu belongsTo AdminUsers (by updatedBy = username).
Feature 7 — Tomorrow-meals-count WhatsApp job¶
What it is¶
Nightly automated WhatsApp messages that tell the kitchen how many of each meal are booked for tomorrow, plus alerts if that count changes late in the evening. Lives in cron.js + helpers/whatsapp.helper.js, timezone Asia/Kolkata.
Backend rules & logic¶
- 9 PM job (
0 21 * * *→sendTomorrowMealsCount([...])): sums tomorrow's meals fromfood_db(SUM(CASE WHEN meal=1)) plusbulk_food_booking(SUM(meal)counts) for each meal, writes the totals as a baseline tolast_meals_count.json({tomorrowDate, breakfast, lunch, dinner}), and sends thedaily_kitchen_meal_counttemplate to recipients['0002849952', '0012754172', '0002823407']. [src: cron.js:286,289; helpers/whatsapp.helper.js:2418,2430-2440,2457,2482] - 10 PM & 11 PM jobs (
checkAndSendMealsCountUpdate): recompute tomorrow's totals, compare to the baseline inlast_meals_count.json(only if itstomorrowDatematches). If changed → resend the count to Maharaj only (0002823407). If no valid baseline exists (e.g. after a server restart) → save the current count as baseline and skip the notification to avoid a false alert. [src: cron.js:300,313; helpers/whatsapp.helper.js:2528,2543-2559] - The count = individual
food_dbbookings + bulk group counts, so it reflects both members and event/department guests.
Discrepancy note¶
last_meals_count.json is a file on the app server's working directory — not durable across multiple instances or ephemeral filesystems (change-detection can misfire in a multi-node / container deploy).
Feature 8 — Reports¶
foodReport — GET /api/v1/admin/food/report¶
Per-date rollup over [start_date, end_date] merging three sources. For each date it returns, from food_db: breakfast/lunch/dinner booked, *_plate_issued, *_noshow (booked − issued), coffee/tea (hightea), non_spicy; from food_physical_plate: *_physical_plates (K1 counts); from bulk_food_booking: *_guest_count, *_guest_issued, *_guest_noshow. Optional ?ignore_events=true excludes utsav meal-periods: it walks each overlapping UtsavDb event using its starting_meal/ending_meal to zero-out the covered meals per day and drops fully-covered days.
foodReportDetails — GET /report_details¶
Individual-booking drill-down for a (meal, date, is_issued): lists food_db rows joined to CardDb (issuedto, mobno), ordered by name. Validates meal ∈ {breakfast, lunch, dinner}.
foodReportDetailsGuests — GET /report_details_guests¶
Bulk-booking drill-down for a (meal, date, is_issued): lists bulk_food_booking rows where the meal count > 0, with a computed pending_plates = meal − meal_plate_issued; is_issued=1 → only rows with plates issued, 0 → only rows still pending.
getMealCountByMobile — POST /meal-count¶
Per-person summary by mobile over [fromDate, toDate]: sums booked vs issued per meal from food_db joined to CardDb, excluding dates inside any overlapping Utsav at "Research Centre" (returned as utsavExcluded so the UI can explain the exclusions). Returns {message, data, person, utsavExcluded}.
Admin operations¶
Screens: Food Reports (foodReport), issued-plate & issued-guest-plate reports (issuedPlateReport, issuedGuestPlateReport), Meal Count (mealCount). The Food Management index also links Issue Plate, Manage Food Bookings, Manage Guest Food Bookings, Plates Issued, Menu Management, and Issue Plate – Scanner. fetch_food_bookings (GET) backs "Manage Food Bookings" (upcoming meals for a card/mobile where at least one meal is set).
Every route¶
| Method | Path | Auth realm | Purpose | Key params/body |
|---|---|---|---|---|
| GET | /api/v1/admin/food/report |
superAdmin/foodAdmin/smilesAdmin | Per-date meal + plate rollup | ?start_date=&end_date=&ignore_events= |
| GET | /api/v1/admin/food/report_details |
superAdmin/foodAdmin/smilesAdmin | Individual bookings for a meal/date | ?meal=&date=&is_issued= |
| GET | /api/v1/admin/food/report_details_guests |
superAdmin/foodAdmin/smilesAdmin | Bulk (guest) bookings for a meal/date | ?meal=&date=&is_issued= |
| POST | /api/v1/admin/food/meal-count |
superAdmin/foodAdmin/smilesAdmin | Per-mobile booked/issued summary | {mobno, fromDate, toDate} |
| GET | /api/v1/admin/food/fetch_food_bookings |
superAdmin/foodAdmin/smilesAdmin | Upcoming meals for a card/mobile | ?cardno= or ?mobno= |
Feature 9 — Guest vs member food differences¶
| Aspect | Member (PR / MUMUKSHU / SEVA KUTIR) | Guest (res_status = GUEST) |
Bulk / department |
|---|---|---|---|
| Storage | food_db boolean row per person-day |
food_db boolean row per person-day, bookedBy = booker |
bulk_food_booking integer counts |
| Charged? | Free (no transaction) | Charged ₹60/₹120/₹120 per new meal, billed to booker | Not billed |
| Prerequisite | MUMUKSHU must have a stay booked (validateFood); PR/seva-kutir exempt |
Exempt (GUEST status passes validateFood) |
Admin-created only |
| Cancellation refund | Nothing to refund | Linked transaction reversed on cancel | No transaction |
| Skipped for PR self-bookings (high volume); sent for others | Sent (booker vs attendee templates, NRI variants) | bulk_food_booking_confirmed/_updated |
|
| Created via | booking engine / admin book |
booking engine /guest/booking / admin book |
admin /bulk_booking only |
Edge cases & branches (cross-feature)¶
- 11 AM booking cutoff is client-side. The general food-booking API (
bookFoodForMumukshus) does not enforce an 11 AM previous-day cutoff — it only runsvalidateDatefor date validity. The 11 AM rule is enforced only by the member-app calendar's minimum date and by admin bulk booking for thesmilesAdminrole (403). Any client that skips the calendar gating could book same-day/late. [src: helpers/foodBooking.helper.js:104; controllers/admin/foodManagement.controller.js:391-410] - 8 PM cancellation cutoff applies only to the member
cancelFoodpath; admin cancels bypass it. - Plate auto-detect on non-today dates:
issueFoodPlatelooks up the booking ontargetDatebut auto-detects the meal against the current clock, so issuing for a non-today date without an explicit meal is meaningless; after 7 PM auto-detect fails entirely (ERR_INVALID_MEAL_TIME). - OR-merge on rebooking means a member can add meals to an existing day but the booking path never removes meals — removal only happens through cancel.
editBulkBookingoverwritesguestCountwith the max of the meal counts, so the stored guest count is derived, and its smilesAdmin cutoff (8 PM) differs frombulkBooking's create cutoff (11 AM).- Menu shape mismatch: member
/food/menureturns a date-keyed object with display times; admin/admin/food/menureturns a flat array without times. - Deprecated code:
bookFoodForMumukshusDuringUtsav_DEPRECATEDremains in the helper but is unused (superseded bygetDatesDuringUtsav).
Discrepancies¶
foodratetable is dead. TheFoodRatemodel (models/food_rate.model.js, tablefoodrate:mealtype,rate,updatedBy) is never read or written anywhere in backend or admin (it isn't even imported inassociations.js); all prices are hardcoded constants (BREAKFAST_PRICE=60,LUNCH/DINNER=120). There is no admin UI to change food prices. Either wire pricing to the table or drop the table.[src: models/food_rate.model.js (only self-reference); config/constants.js:22-24]- Inconsistent smilesAdmin cutoffs: bulk create blocks tomorrow after 11 AM, but bulk edit blocks tomorrow after 8 PM — same booking, two different deadlines.
[src: controllers/admin/foodManagement.controller.js:400 (create 11:00), 565 (edit 20:00)] - Guest self-paid meals aren't refunded on cancel.
cancelFoodonly reverses a transaction whenbookedForis set (the// FIXME: guests can book self meals toonote) — a guest-status person who booked and paid for their own meal would have the meal cancelled but the charge not reversed.[src: helpers/foodBooking.helper.js:443-455] last_meals_count.jsonis filesystem state, not DB — meal-count change-detection is fragile on multi-instance or ephemeral deployments.[src: helpers/whatsapp.helper.js:2418]- No unique constraint on
food_db (cardno, date); the single-row-per-person-day invariant is enforced only in application code, so a race or a bypassing writer could create duplicates that the read/cancel logic doesn't expect.[src: models/food_db.model.js (no unique index)] updatePlateIssued(bulk) restricts to today, but individualissueFoodPlate/bulkIssuePlateaccept an arbitrarydate— inconsistent "issue only today" policy between individual and bulk plates.[src: controllers/admin/foodManagement.controller.js:661-666 (bulk today-only); helpers/foodBooking.helper.js:646-648 (individual accepts date)]
Removed this pass: a former discrepancy #2 claiming the admin "Meal Count" page is broken (checks a nonexistent
result.success). Verified false —aashray-admin/admin/food/mealCount.js:77branches onresponse.ok(HTTP status) and rendersresult.person/data/utsavExcludedcorrectly. The backend response{message,data,person,utsavExcluded}indeed has nosuccessfield, but the page never reads one, so it works.[src: aashray-admin/admin/food/mealCount.js:77,83-85; aashray-backend/controllers/admin/foodManagement.controller.js:1189-1194]
How this connects to other domains (technical)¶
- Booking Lifecycle & Engine (business-logic file 03): food is never created through its own controller — it is a
booking_type: foodslice of themumukshu/guestbooking engine, sovalidateFood, group assembly, and the utsav date-splitting all run inside that engine. - Payments, Credits & Reconciliation (business-logic file 08): guest meals create
Transactions(category= meal name,bookingid=food_db.id) billed to the booker, consume food credits, and are reversed on cancel; the 30-minute payment-timeout cron auto-cancels unpaid meals. - Stay, Rooms & Flats (business-logic file 04):
validateFoodrequires MUMUKSHU-status users to have a room/flat booked on the dates before they can order food, tying food eligibility to the stay domain. - Adhyayan & Utsav (business-logic file 07): festival packages bundle their own food (
bookFoodForAllMeals/cancelUtsavFoodBookings); individual food only covers early-arrival / late-departure days, and reports/meal-counts can exclude utsav periods. - Admin Panel Map (11): the Food Management admin area (Issue Plate, Scanner, Manage/Guest bookings, Plates Issued, Reports, Menu, Meal Count) is gated by
foodAdmin/superAdmin/smilesAdmin, with the K1 physical-plate counter additionally open tofoodPlateAdmin. - Notifications: food confirmations, bulk-booking confirmations, and the nightly kitchen meal-count all go out over the shared WhatsApp helper (
sendFoodWhatsApp,sendTomorrowMealsCount).