Skip to content

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.md on 2026-07-07 against aashray-backend main (with the aashray-admin panel 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 HTTP response.ok, not a missing success field, 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 → bookFoodbookFoodForMumukshus 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's res_status is one of PR / SEVA KUTIR / GUEST, or the caller has foodAdmin/superAdmin role, 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), and hightea, spicy, updatedBy are overwritten. Otherwise a new row is bulkCreated with id = uuidv4(), plateissued: 0. [src: helpers/foodBooking.helper.js:161-184,214]
  • bookedBy is set to the payer's cardno when booking for someone else, null when booking for self (bookedBy !== mumukshu ? bookedBy : null). [src: helpers/foodBooking.helper.js:178]
  • Pricing / charging: only cards with res_status == GUEST are charged. For each newly-added meal (not one that already existed) a Transactions row is created with category = breakfast/lunch/dinner, amount = the per-meal price, cardno = the payer (bookedBy), status = cash pending (if cashAllowed, i.e. admin) or pending (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 before start_date and days after end_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 (sendUnifiedWhatsAppsendFoodWhatsApp).

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

  • bookFoodForMumukshus groups accept either mumukshus or guests arrays (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]).
  • FetchFoodBookings builds three UNION ALL sub-selects (one per meal) then filters — bookedFor=self maps to bookedBy IS NULL; a numeric bookedFor maps to cardno = <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 application findOne logic.
  • bookFoodForAllMeals / cancelAllMeals helpers exist for utsav-package food (auto-book all meals across a stay, honoring starting_meal/ending_meal on the first/last day, forcing spicy=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 a foodrate table + FoodRate model (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).
  • checkFoodAvailabilityForMumumkshus computes the preview charge: for guest bookings it clones the user's credits, sums the charge for each new (not-already-booked) meal, and returns usableCredits(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 day at 20:00 IST; the cancel is allowed only if now ≤ cutoff. Items past cutoff are silently filtered out; if food_data was non-empty but all items were filtered → 400 with the 8 PM message. [src: helpers/foodBooking.helper.js:398-423 (cutoff :409-415)]
  • Admin cutoff (admin = true): validDate = today; any item with date >= today is allowed (no 8 PM restriction). [src: helpers/foodBooking.helper.js:402-406]
  • For each valid item it finds the food_db row by (bookedFor || cardno, date), calls cancelMeal (sets that one meal boolean to 0, stamps updatedBy). If the meal was a guest meal (bookedFor present) it locates the matching Transactions row (by bookingid + category = mealTypeMapping[mealType]) and appends it for cancelTransactions (which refunds/credits per the payments rules).
  • A // FIXME: guests can book self meals too note flags that transaction reversal only triggers when bookedFor is set.

Admin operations

  • PUT /api/v1/admin/food/cancel/:bookingid?mealType= (cancelBooking) — cancels one meal on one food_db row (looked up by id + that meal being true), reverses any linked transaction via adminCancelTransaction. No date cutoff.
  • PUT /api/v1/admin/food/cancel_multiple (cancelMultipleMeals) — body meals: [{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 via cancelMealscancelMeal (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

  • cancelMeal only 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): requires cardno or mobno; resolves the CardDb; creates a BulkFoodBooking with breakfast/lunch/dinner = guestCount if selected else 0, plate-issued counts 0, chosen department, updatedBy. No charge is computed (bulk food is not billed through Transactions). Sends a bulk_food_booking_confirmed WhatsApp to the card's phone.
  • smilesAdmin time restriction: if the caller has role smilesAdmin and date is tomorrow and now is after 11:00 AM today403 "You can't book food for tomorrow after 11:00 AM today."
  • editBulkBooking (PUT /edit_bulk_booking/:bookingid): updates the three meal counts; guestCount is recomputed as max(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). Sends bulk_food_booking_updated WhatsApp.

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 /physicalPlates group is superAdmin/foodAdmin/foodPlateAdmin; every other admin food route (issue, book, cancel, bulk booking, plate count, reports, menu, meal-count) is superAdmin/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 from food_db (SUM(CASE WHEN meal=1)) plus bulk_food_booking (SUM(meal) counts) for each meal, writes the totals as a baseline to last_meals_count.json ({tomorrowDate, breakfast, lunch, dinner}), and sends the daily_kitchen_meal_count template 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 in last_meals_count.json (only if its tomorrowDate matches). 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_db bookings + 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
WhatsApp 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 runs validateDate for date validity. The 11 AM rule is enforced only by the member-app calendar's minimum date and by admin bulk booking for the smilesAdmin role (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 cancelFood path; admin cancels bypass it.
  • Plate auto-detect on non-today dates: issueFoodPlate looks up the booking on targetDate but 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.
  • editBulkBooking overwrites guestCount with the max of the meal counts, so the stored guest count is derived, and its smilesAdmin cutoff (8 PM) differs from bulkBooking's create cutoff (11 AM).
  • Menu shape mismatch: member /food/menu returns a date-keyed object with display times; admin /admin/food/menu returns a flat array without times.
  • Deprecated code: bookFoodForMumukshusDuringUtsav_DEPRECATED remains in the helper but is unused (superseded by getDatesDuringUtsav).

Discrepancies

  1. foodrate table is dead. The FoodRate model (models/food_rate.model.js, table foodrate: mealtype, rate, updatedBy) is never read or written anywhere in backend or admin (it isn't even imported in associations.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]
  2. 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)]
  3. Guest self-paid meals aren't refunded on cancel. cancelFood only reverses a transaction when bookedFor is set (the // FIXME: guests can book self meals too note) — 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]
  4. last_meals_count.json is filesystem state, not DB — meal-count change-detection is fragile on multi-instance or ephemeral deployments. [src: helpers/whatsapp.helper.js:2418]
  5. 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)]
  6. updatePlateIssued (bulk) restricts to today, but individual issueFoodPlate/bulkIssuePlate accept an arbitrary date — 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:77 branches on response.ok (HTTP status) and renders result.person/data/utsavExcluded correctly. The backend response {message,data,person,utsavExcluded} indeed has no success field, 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: food slice of the mumukshu/guest booking engine, so validateFood, 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): validateFood requires 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 to foodPlateAdmin.
  • Notifications: food confirmations, bulk-booking confirmations, and the nightly kitchen meal-count all go out over the shared WhatsApp helper (sendFoodWhatsApp, sendTomorrowMealsCount).