Skip to content

Operational Services — WiFi, Maintenance, Support & Gate — Technical Reference

Part of the Aashray Technical Reference. Pairs with Business Logic — Services, which describes these rules in plain terms with no code detail. Related technical detail currently lives inline in: Accounts, Identity & Auth, Admin Panel Map, API Reference (not yet split out into their own technical files).

This file is 100% derived from reading the backend and admin source on main — no operational/staff-knowledge content lives here (that's in the business-logic companion). It covers the four "everyday operations" services that hang off a member's identity rather than off a booking: campus WiFi credentials (temporary + permanent), maintenance requests (facility repairs routed to departments), support tickets (free-form help requests), and the physical gate check-in/check-out system that toggles a member's on-premises status. All four are anchored to the same hub — CardDb.cardno — and are almost entirely stateless w.r.t. the booking flows, except that WiFi and maintenance eligibility read a member's current check-in state, and the gate flow writes check-in state back onto room/flat bookings.

Verification status: audited & anchored per docs/DOCS-METHODOLOGY.md on 2026-07-07 against aashray-backend main (with aashray-admin cross-checked). Every rule carries a [src: …] anchor. Correction this pass: the deep-cleaning WhatsApp recipient list was documented as two cardnos but is actually a ~20-entry hardcoded whitelist — fixed below. All discrepancies (inverted WiFi status, gate entry/exit asymmetry, gateExit null-guard, no support admin surface, etc.) confirmed real against code.

Two auth realms appear throughout: - Member realm — routes under /api/v1/{wifi,maintenance,support} guarded by validateCard (middleware in middleware/validate.js). It reads cardno from req.params/req.body/req.query, loads the CardDb row into req.user, and 404s (Cardno not provided / User not found) if absent. There is no password/JWT on the member realm — possession of a valid cardno is the only credential. - Admin realm — routes under /api/v1/admin/* guarded by auth + authorizeRoles(...) (in middleware/AdminAuth.js): a JWT Bearer token identifies an AdminUsers row, AdminRoles supplies the role list, and each router allow-lists specific roles. The admin web panel (repo admin-main) is plain HTML/JS calling these endpoints with CONFIG.basePath = https://aashray.vitraagvigyaan.org/api/v1/admin.


1. WiFi

Data model

Campus network access is backed by two tables: - wifi_pwd (WifiDb): pwd_id PK, cardno (nullable FK → card_db.cardno), password (not null), roombookingid (nullable FK → room_booking.bookingid), status ENUM(active,inactive) default active, updatedBy, timestamps. WifiDb.belongsTo(CardDb). - permanent_wifi_codes (PermanentWifiCodes): id PK, cardno (not null FK → card_db.cardno), username (not null), code/ssid (nullable), status ENUM(pending,approved,rejected,reset,deleted) default pending, requested_at(NOW default)/reviewed_at/reviewed_by/admin_comments. CardDb.hasOne(PermanentWifiCodes) + PermanentWifiCodes.belongsTo(CardDb).

Inverted status semantics on wifi_pwd — this is a footgun worth flagging on its own: status='active' means "unclaimed / available in the pool," and status='inactive' means "claimed by a member." The naming is the opposite of what a reader would assume from the words alone; the generate flow succeeds only if the pool still has rows with status='active'. [src: models/wifi.model.js:8-39; controllers/wifi/wifi.controller.js:64,68 (claims 'active' → sets 'inactive')]

Member-app behavior

The WiFi screen (Home → "Wifi" tile, app route (home)/wifi) shows a Permanent section to everyone and a Temporary section only to non-residents (res_status not PR and not SEVA KUTIR). On load it fires GET /wifi (temporary list) and GET /wifi/permanent (permanent status cards). Actions: request a permanent code (device-type picker for PR/Seva Kutir; non-residents submit silently with device Mobile), reset an approved permanent code (confirm dialog), and generate a temporary code (disabled once the 1-code limit is hit). All codes/usernames/SSIDs are tap-to-copy. App-side instruction copy claims permanent codes are "valid for 1 year" and temporary codes "valid for 2 weeks with a data limit" — neither validity window is enforced or stored by the backend (see Discrepancies).

Backend rules & logic

Temporary codes (wifi_pwd, controller controllers/wifi/wifi.controller.js): - The pool is seeded by admins (bulk Excel upload) as rows with cardno = null, roombookingid = null, status = 'active'. - generateTempCode (GET /wifi/generate): [src: controllers/wifi/wifi.controller.js:26-93,329-364] 1. Eligibility: res_status must be MUMUKSHU or GUEST, else 403 "You are not eligible to generate a temporary WiFi code". 2. Must be checked in: fetchBookings(cardno) looks for a RoomBooking, FlatBooking, or UtsavBooking with status = 'checkedin' and checkout/end_date >= today; if none → 404 "user not checked in yet.". The claimed booking's bookingid becomes roombookingid. 3. Limit: counts existing inactive rows for this cardno + roombookingid; if >= MAX_WIFI_PASS_LIMIT (= 1, defined in wifi.controller.js:26, not constants.js) → 400 "Cannot generate more than 1 passwords". 4. Claim: UPDATE one status='active' pool row (ordered by pwd_id ASC, limit 1) setting cardno, roombookingid, status='inactive'. If nothing was updated (empty pool) → 404 "No available WiFi codes to assign. Please try again later.". Returns the claimed password (HTTP 200). Wrapped in a DB transaction. - fetchTempCodes (GET /wifi): non-MUMUKSHU/GUEST get 200 with data: []; if not checked in, 200 "No active bookings found" + []; otherwise returns all wifi_pwd rows for this cardno + current bookingid (password, createdAt).

Permanent codes (permanent_wifi_codes): - requestPermanentCode (POST /wifi/permanent, body deviceType): requires deviceType (400 if missing). For MUMUKSHU/GUEST only, blocks if an existing request is pending or approved (400 with a status-specific message). PR / Seva Kutir are not blocked, so they may hold multiple pending/approved rows (one per device — matches the app's per-device model). [src: controllers/wifi/wifi.controller.js:132-208] Username generation: strip a leading guest-; drop ignore-prefixes (rcof, rchk, cons, chak, divi, paon, guest); username = <firstName><lastName><last4ofCardno><deviceSuffix> where suffix map is mobile→ph, laptop→pc, tablet→tb, else→ot; a numeric counter is appended to de-dup against existing approved/reset/pending usernames. Creates the row status='pending', requested_at=now, then fires sendWifiRequestWhatsApp(...). Returns 201. - fetchPermanentCodes (GET /wifi/permanent): returns all rows for the cardno except deleted, exposing id, username, code, ssid, status, requested_at, reviewed_at, admin_comments. - resetPermanentCode (POST /wifi/permanent/reset, body id): must reference an approved row owned by the cardno (404 otherwise); flips it to status='reset', WhatsApp-notifies. Re-enters the admin queue.

Every route

Method Path Auth realm Purpose Key params/body
GET /api/v1/wifi Member (validateCard) List this stay's temporary codes cardno (query)
GET /api/v1/wifi/generate Member Claim one temporary code from pool cardno (query)
POST /api/v1/wifi/permanent Member Request a permanent code cardno, deviceType
GET /api/v1/wifi/permanent Member Fetch permanent request status cards cardno (query)
POST /api/v1/wifi/permanent/reset Member Request reset of an approved code cardno, id
POST /api/v1/admin/wifi/uploadcode Admin (superAdmin, wifiAdmin) Bulk-seed temporary-code pool from Excel multipart file (col password)
GET /api/v1/admin/wifi/wifirecords Admin Temporary-code report (joined to card + room/flat) query startDate,endDate,status,bookingType
GET /api/v1/admin/wifi/permanent Admin List permanent requests (filterable) query status, requestType (pending-new|pending-reset)
PUT /api/v1/admin/wifi/permanent/:requestId Admin Approve/reject/reset/delete/re-pending a request body action, permanent_code, admin_comments, ssid, username
POST /api/v1/admin/wifi/uploadpercode Admin Bulk-update permanent codes from Excel (supports ?dryRun=true) multipart file
POST /api/v1/admin/wifi/insertpercode Admin Bulk-insert permanent codes from Excel ?allowInsert=true (required), ?dryRun=true
POST /api/v1/admin/wifi/manual Admin Manually add one approved permanent code body mobno, cardno, ssid, deviceType, username, code
GET /api/v1/admin/wifi/generate-username Admin Preview a generated username query cardno, issuedto, deviceType
GET /api/v1/admin/wifi/permanent/portal-export Admin Export approved codes as portal-account XLSX query hours OR startDate+endDate

Admin operations

Admin panel WiFi pages live under admin-main/admin/wifi/ (index.html, wifiReport.html/js, permanentCodeRequests.html/js, uploadCodes.js), gated to wifiAdmin / superAdmin. - Temporary: uploadCodes.jsPOST /wifi/uploadcode seeds the pool (dedupes on password, ignores duplicates). wifiReport.jsGET /wifi/wifirecords lists claimed codes with holder + room/flat check-in dates. - Permanent: permanentCodeRequests.js drives the whole lifecycle. It lists via GET /wifi/permanent (status filter or requestType=pending-new/pending-reset), then per-row calls PUT /wifi/permanent/:requestId with action. Backend validations on update: action ∈ {approved,rejected,deleted,reset,pending}; can't delete an already-deleted row; only an approved row may move to reset; approving a code-less (pending-new) row requires permanent_code; a duplicate approved code across users is rejected with the conflicting cardno. Approving sets code; resetting nulls code; every update stamps reviewed_at/reviewed_by/admin_comments and WhatsApps the member. The page also does manual add (POST /wifi/manual, which looks up the card by cardno+mobno), username preview (GET /wifi/generate-username), bulk update/insert (uploadpercode/insertpercode, both with a dryRun diff preview), and portal export (portal-export, which derives First_name/Last_name/User_group from username + res_status — PR→Residents, Seva Kutir→1Yr1DeviceUnlimitedData, Mumukshu/Guest→NonPRpermanentCode).

Edge cases & branches

  • Temporary eligibility is doubly gated: res_status ∈ {MUMUKSHU, GUEST} and an active check-in (room/flat/utsav). PR/Seva Kutir never see the section; a checked-out member gets an empty list.
  • Permanent duplicate-request block applies only to Mumukshu/Guest; PR/Seva Kutir intentionally bypass it.
  • Bulk insertpercode is safety-gated: without ?allowInsert=true it returns 400 "Insert not allowed. Confirm by sending allowInsert=true". Both bulk endpoints support ?dryRun=true to preview changes without committing, and auto-generate a username when the Excel/DB username is blank.
  • WhatsApp sends are fire-and-forget (setTimeout, 200ms throttle for bulk) — a WhatsApp failure never fails the request.

Discrepancies

  • Validity windows are fiction at the backend. The app advertises "1 year" (permanent) and "2 weeks + data limit" (temporary), but no expiry, TTL, or data-cap column exists and nothing ever expires a code. Enforcement (if any) lives on the network appliance / portal export, not in Aashray. [src: models/wifi.model.js; models/permanent_wifi_codes.model.js (no expiry/TTL/data columns)]
  • deviceType is used but not modeled. requestPermanentCode, addPermanentCodeManually, and the bulk importers read/write deviceType, but the Sequelize model (models/permanent_wifi_codes.model.js) does not declare a deviceType column. (In requestPermanentCode deviceType is only used to build the username/WhatsApp and is not passed to .create(), so no dropped write there; the manual/bulk paths may still write it against an unmodeled column.) [src: controllers/wifi/wifi.controller.js:132,243-251; models/permanent_wifi_codes.model.js]
  • The commented-out original wifi.routes.js referenced a deletePermanentCode client endpoint; deletion is now admin-only (action:'deleted').
  • wifiRecord builds SQL by string concatenation with :replacements — parameterized, but bookingType filter joins both room_booking and flat_booking on roombookingid, so a flat-claimed temp code appears under bookingType=flat correctly but the join is wide.

2. Maintenance

What it is

Member-filed facility work requests (electrical / housekeeping / general maintenance) routed to a department, tracked through an open → in progress → closed lifecycle, and worked by department-scoped admins. A separate deep-cleaning tracker for flats rides on the same admin router.

Member-app behavior

Home → "Maintenance" tile → app route (home)/maintenanceRequestList ("Maintenance History"). A paginated, status-filterable (All/Open/Closed) list of the member's own requests as expandable cards, plus a floating "+" that opens a request form (Department single-select, multiline Detail of Work, single-line Place). Submit posts the request; list refetches. Department options shown are "Electrical", "House Keeping", "Maintenance" (the server key for "House Keeping" differs from the label).

Backend rules & logic

Controller controllers/client/maintenanceRequest.controller.js: - CreateRequest (POST /maintenance/request): Eligibility — if res_status is not PR and not SEVA KUTIR, the member must have a RoomBooking with status='checkedin' (only RoomBooking — not flat or utsav), else 400 "You are not checked in". PR/Seva Kutir may file anytime. Creates a MaintenanceDb row with bookingid = uuidv4(), requested_by = cardno, department, work_detail, area_of_work (nullable), updatedBy='USER', default status='open'. Looks up the department's dept_email (400 "Department not found" if missing), emails the member (cc department) via the maintainanceRequest template, commits, then fires the maintenance_request_received WhatsApp template. Returns 201. [src: controllers/client/maintenanceRequest.controller.js:32-135] - ViewRequest (GET /maintenance): paginated (page, page_size default 10) list of the member's own requests (requested_by = cardno), optional status filter (default all), ordered createdAt DESC, excluding updatedAt/updatedBy. - FetchDepartments (GET /maintenance/departments): returns all Departments rows (used to populate the picker).

Every route

Method Path Auth realm Purpose Key params/body
POST /api/v1/maintenance/request Member (validateCard) File a maintenance request cardno, department, work_detail, area_of_work?
GET /api/v1/maintenance Member List own requests (paginated, filtered) cardno, page?, page_size?, status?
GET /api/v1/maintenance/departments Member List departments for the picker cardno (query)
GET /api/v1/admin/maintenance/fetch/:department Admin (superAdmin,maintenanceAdmin,housekeepingAdmin,electricalAdmin) Department work queue (prioritized) :department path
PUT /api/v1/admin/maintenance/update Admin Update comments/status (close notifies member) body bookingid, comments, status
GET /api/v1/admin/maintenance/housekeeping/deep-cleaning/status Admin Flat deep-cleaning status per flat
POST /api/v1/admin/maintenance/housekeeping/deep-cleaning/done Admin Mark deep cleaning done body flatno|flatnos[], cleaningDate?
POST /api/v1/admin/maintenance/housekeeping/deep-cleaning/interval Admin Set a flat's cleaning interval body flatno, interval

Admin operations

Admin pages under admin-main/admin/maintenance/ and admin-main/admin/housekeeping/, gated to superAdmin + the three department admins. - maintenance.js reads ?department=maintenance|housekeeping|electrical from the URL and calls GET /maintenance/fetch/:department. fetchMaintenanceReport returns rows for that department (statuses open/in progress/closed), joined to CardDb (issuedto,mobno), ordered by a status-priority literal (open=0, in progress=1, closed=2) then createdAt DESC, and computes a virtual closedAt (= updatedAt when closed). - updateRequest.jsPUT /maintenance/update. updateMaintenanceRequest updates comments and status on the row (found by bookingid, included with CardDb as 'card'). When it transitions into closed (and wasn't already), it sends the maintenance_request_closed WhatsApp template to the member. Note: a department field in the body is accepted but ignored (reassignment is commented out). - Deep-cleaning tracker (housekeeping/deepcleaning.js) is orthogonal to member requests — it operates on FlatDb (last_deep_cleaning, deep_cleaning_interval, deep_cleaning_history JSON), and only WhatsApps a hardcoded ~20-cardno whitelist (DEEP_CLEANING_WA_RECIPIENTS, starting '0002945690','0009076440','0002819369','0012247780',…). [src: config/constants.js:173+]

Data & relationships

  • maintenance_db (MaintenanceDb): bookingid PK (UUID string), requested_by (FK → card_db.cardno), department (FK → departments.dept_name), work_detail (not null), area_of_work/comments (nullable), status ENUM(open,closed,in progress) default open, finished_at INTEGER (nullable, unused), updatedBy. Associations: MaintenanceDb.belongsTo(CardDb, {as:'card', fk:'requested_by'}), MaintenanceDb.belongsTo(Departments, {fk:'department'→'dept_name'}); CardDb.hasMany(MaintenanceDb); Departments.hasMany(MaintenanceDb).
  • departments (Departments): dept_name PK, dept_head, dept_email, updatedBy.

Edge cases & branches

  • Non-resident eligibility checks only RoomBooking check-in — not flat or utsav (unlike WiFi's fetchBookings). A non-resident staying only via a flat or utsav booking is told "You are not checked in" and cannot file a request.
  • The status priority sort surfaces open work first; closedAt is derived, not stored.
  • Email cc goes to the department mailbox; both email and WhatsApp are best-effort and log-and-continue on failure.

Discrepancies

  • Flat/utsav guests are locked out of filing requests (room-only check-in gate) — likely unintended given the housekeeping tracker targets flats. [src: controllers/client/maintenanceRequest.controller.js:32-44]
  • finished_at column exists but is never written. [src: models/maintenance_db.model.js (finished_at declared, no writes anywhere)]
  • Admin updateMaintenanceRequest cannot reassign departments despite the request body accepting department (dead field). [src: controllers/admin/maintenanceManagement.controller.js:82,106]

3. Support

What it is

A minimal, write-only "general help" ticket: a member picks a service category and writes a free-text issue.

Member-app behavior

Home → "Support" tile → app route (home)/support ("Support Ticket"). Two fields — service single-select ("Booking Related Issues", "Payment Related Issues", "WiFi Related Issues", "Other") and a multiline description (client-side must be ≥ 10 characters). Submit posts the ticket, shows a success alert, resets, and navigates back. A back-discard guard warns if fields have content.

Backend rules & logic

Controller controllers/client/support.controller.js, single handler createTicket (POST /support): opens a transaction, creates a SupportTickets row (issued_by = cardno, service, issue), commits, returns 201 {success:true, message:'Ticket created successfully'}. No server-side validation of service/issue content or length (length is enforced only in the app). An email notification to tech@vitraagvigyaan.org exists in code but is commented out. [src: controllers/client/support.controller.js:13-38 (commented email :25-33); models/support_tickets.model.js:7-28 (no status/response columns)]

Every route

Method Path Auth realm Purpose Key params/body
POST /api/v1/support Member (validateCard) Create a support ticket cardno, service, issue

Admin operations

None found. There is no routes/admin/support* router, no admin support controller, and no admin panel page for support in admin-main. Tickets are currently readable only by direct database access.

Data & relationships

  • support_tickets (SupportTickets): id PK auto-increment, issued_by (not null FK → card_db.cardno), service (not null), issue TEXT (not null), timestamps. SupportTickets.belongsTo(CardDb, {fk:'issued_by'}); CardDb.hasMany(SupportTickets).

Edge cases & branches

  • No length/format validation server-side; a client bypassing the app could submit an empty/short issue.
  • Transaction wraps a single insert (no partial-failure surface).

Discrepancies

  • No admin surface exists for support tickets despite this being a "ticket" system — no list, no status, no assignee, no reply. The model has no status/resolved/response columns; a ticket is fire-and-forget. The disabled email means staff currently receive no notification either. This is the largest functional gap of the four services. [src: no routes/admin/support*, no admin support controller; models/support_tickets.model.js:7-28]

4. Gate

What it is

The physical entry/exit system. A member's QR (which encodes only their cardno) is scanned at the gate; the backend records an on-prem/off-prem event, toggles the member's live presence status on CardDb, and cascades check-in/check-out onto that member's room/flat bookings. It also powers the gate dashboard head-counts by resident class.

Member-app behavior

Members have no gate API — their only role is to present their identity QR. The app renders the cardno as a QR in two places (quick modal from the tab bar's floating QR button, and a full-screen ticket at profile/qr); both encode the raw cardno (the gate scanner also accepts a cardnumber=<cardno> prefixed payload and strips it). Staff at the gate scan it. So the "gate flow" is entirely an admin/staff operation triggered by the member's QR.

Backend rules & logic

Controller controllers/admin/gateManagement.controller.js, all inside /api/v1/admin/gate (roles gateAdmin, superAdmin): - gateEntry (POST /gate/entry, body cardno): loads the CardDb (404 "User not found" if absent); if status == offprem sets status = onprem; inserts a GateRecord (status='onprem', updatedBy = admin username). On response finish (post-commit, best-effort), auto-checks-in both a FlatBooking and a RoomBooking whose status='pending checkin' and checkin = today → sets them checkedin. Returns {cardno, issuedto}. - gateExit (POST /gate/exit, body cardno): loads CardDb, sets status='offprem', inserts a GateRecord (status='offprem'), and auto-checks-out a FlatBooking whose status='checkedin' and checkout <= todaycheckedout. Returns {cardno, issuedto}. - gateRecord (GET /gate/gaterecords): raw SQL, all gate records joined to card_db (issuedto,mobno), newest first. - fetchGateHistoryByCard (GET /gate/history/:cardno): all GateRecord rows for one card, newest first. - Head-count dashboards: fetchTotal (/gate/total, counts status=onprem grouped by res_status), and fetchPR/fetchGuest/fetchMumukshu/fetchSevaKutir (/gate/totalPR|totalGuest|totalMumukshu|totalSeva), each returning currently-on-prem cards of that res_status with correlated-subquery last_checkin (MAX createdAt where gate status onprem) and last_checkout (MAX where offprem).

Entry/exit asymmetry (the core defect): gateEntry auto-checks-in both a RoomBooking and a FlatBooking whose status is pending checkin and whose checkin date is today. gateExit only auto-checks-out a FlatBooking whose status is checkedin and whose checkout date has arrived — there is no equivalent room-booking branch in gateExit. The two handlers are not mirror images of each other, and a room-based stay is never automatically closed out at the gate. [src: controllers/admin/gateManagement.controller.js:200-224 (entry: flat + room); :262-273 (exit: flat only)]

Missing null guard (latent bug): gateEntry explicitly checks for a missing CardDb row and returns 404 "User not found". gateExit does not perform the same check — it calls .update(...) directly on the lookup result, so an unknown cardno throws when Sequelize tries to call .update on null, surfacing an unhandled 500 instead of a clean 404. [src: controllers/admin/gateManagement.controller.js:175-181 (entry guard); :242-249 (exit, no guard)]

Every route

Method Path Auth realm Purpose Key params/body
GET /api/v1/admin/gate/total Admin (gateAdmin,superAdmin) On-prem head-count grouped by res_status
GET /api/v1/admin/gate/totalPR Admin On-prem PRs + last in/out
GET /api/v1/admin/gate/totalGuest Admin On-prem Guests + last in/out
GET /api/v1/admin/gate/totalMumukshu Admin On-prem Mumukshus + last in/out
GET /api/v1/admin/gate/totalSeva Admin On-prem Seva Kutir + last in/out
POST /api/v1/admin/gate/entry Admin Record entry, set onprem, auto check-in room+flat body cardno
POST /api/v1/admin/gate/exit Admin Record exit, set offprem, auto check-out flat body cardno
GET /api/v1/admin/gate/gaterecords Admin All gate records (joined to card)
GET /api/v1/admin/gate/history/:cardno Admin Gate history for one card :cardno path

Admin operations

Gate pages under admin-main/admin/gate/, gated to gateAdmin/superAdmin. Two capture modes each for in and out: - QR scan (gateIn.html/js, gateOut.html/js) — uses the Html5Qrcode library with the rear camera to scan the member's app QR; processScannedText strips a cardnumber= prefix if present, then POSTs {cardno} to /gate/entry or /gate/exit. On success it shows ✅ QR Code Scanned: {cardno} ({issuedto}). - Manual / scanner-gun entry (gateInTap.html/js, gateOutTap.html/js) — a text input (keyboard or a USB barcode reader) submits a cardno; same POST. Plays an error sound on failure. - gateReport.html/jsGET /gate/gaterecords renders the full event log; the dashboard cards call the total* endpoints.

Data & relationships

  • gate_record (GateRecord): id PK auto-increment, cardno (not null FK → card_db.cardno), status ENUM(onprem,offprem), updatedBy, timestamps. GateRecord.belongsTo(CardDb); CardDb.hasMany(GateRecord) (CASCADE). Every scan appends a new immutable row — presence history is the full event stream, and CardDb.status is the denormalized "current" value.
  • Cross-domain writes: gate entry flips RoomBooking/FlatBooking.status pending checkin → checkedin; gate exit flips FlatBooking.status checkedin → checkedout. These are the physical realization of the booking lifecycle (see the Bookings domain).

Edge cases & branches

  • The auto check-in runs in a res.on('finish') callback outside the transaction and swallows errors — a booking that fails to flip is logged only; the gate event still commits.
  • Presence status is idempotent-ish: entry only writes onprem when currently offprem, but always appends a GateRecord, so repeated scans stack duplicate events.
  • QR payload is trusted verbatim as a cardno — there is no signature or expiry on the identity QR (it is explicitly an identity token, not a secure credential).

Discrepancies

  • Entry/exit asymmetry. gateEntry auto-checks-in both RoomBooking and FlatBooking; gateExit only auto-checks-out FlatBooking. The room-booking auto-checkout omission means room stays can linger in checkedin after the guest physically leaves, unless a room admin closes them elsewhere. [src: controllers/admin/gateManagement.controller.js:200-224,262-273]
  • gateExit's missing null guard is a latent 500: unlike gateEntry, it does not check whether the CardDb lookup returned a row before calling .update on it. [src: controllers/admin/gateManagement.controller.js:242-249]
  • The member app labels this an identity/QR system for "check-in, meals, and other services," but the only backend consumer of the gate scan is the gate controller; meal/other-service scanning is handled by their own controllers reading cardno, not via gate_record.

How this connects to other domains (technical)

  • Accounts & Identity (business-logic file 02): every service is keyed to CardDb.cardno; the member realm's only credential is a valid cardno, and CardDb.res_status (PR/SEVA KUTIR/MUMUKSHU/GUEST) gates WiFi-temporary and maintenance eligibility. CardDb.status (onprem/offprem) is owned and toggled by the gate flow and read by the WiFi/maintenance check-in gates.
  • Bookings & Check-in lifecycle: WiFi-temporary and maintenance both require an active checkedin room/flat/utsav booking; the gate flow is what creates those checkedin/checkedout states by cascading onto RoomBooking/FlatBooking. Bugs here (room not auto-checked-out) ripple into occupancy reporting.
  • Admin Panel & Roles (11): each service maps to dedicated admin roles — wifiAdmin, maintenanceAdmin/housekeepingAdmin/electricalAdmin, gateAdmin (all also superAdmin) — enforced by authorizeRoles. Support has no role because it has no admin surface.
  • Notifications: WiFi (request/approve/reject/reset), maintenance (received/closed), and the housekeeping tracker all emit WhatsApp templates (and maintenance also email); gate and support emit none. All notifications are best-effort and never block the primary write.
  • Departments are a shared reference table also relevant to org/admin config; maintenance is their only functional consumer today.