Skip to content

feat(hostel): add room allocation with bed-level occupancy tracking (fixes #263) - #268

Open
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-263-hostel-management
Open

feat(hostel): add room allocation with bed-level occupancy tracking (fixes #263)#268
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-263-hostel-management

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Related Issue

Closes #263

Description

Adds hostel room allocation with real bed-level accounting, plus an allocation history that is closed rather than overwritten.

A boarder opening /hostel sees their room card — block, room number, bed, warden contact, rent, amenities — a small grid showing the room layout with their own bed marked and who is in the others, and their full allocation history. A non-boarder sees the availability summary.

The warden gets a Dashboard tab: block-wise occupancy bars, a room grid with each bed colour-coded, a bed-level allocation dialog, transfer and vacate actions, and a searchable list of current boarders.

Things worth a reviewer's attention

The bed array is the single source of truth. occupiedBeds, status, bedsAvailable and occupancyRate are all recomputed from beds[] in one pre('validate') hook. Nothing updates them at a call site — that is precisely how the spreadsheet this replaces drifted out of step. Beds are generated from capacity by the model, so a client cannot post a bed list that starts out "occupied".

Shrinking capacity is refused, not silent. If the beds being removed are occupied, the save is rejected naming the count, rather than quietly dropping a student out of the room.

maintenance and closed are warden-set states and are deliberately never overwritten by the derived available/full flip. A room under maintenance also refuses new allocations at the model level, not just in the UI.

Write ordering on allocate and transfer. The room is saved before the allocation row. If the second write fails, the bed is handed back in a catch; the worst surviving case is a bed marked occupied with no row, which the admin recompute-occupancy endpoint repairs by rebuilding every room from the allocation collection. The reverse order would hand one bed to two students, which is the failure that actually hurts. A transfer that fails partway restores the original allocation and re-occupies the old bed.

Mongoose 9 has no multi-document transactions available without a replica set, so this is a compensating-action approach rather than a real transaction — flagging it explicitly in case the project later runs against a replica set and wants to tighten it.

A transfer is a chain, not two unrelated events. The old row closes as transferred and stores transferredTo pointing at the new one, so the history reads properly. Vacating keeps the row and stamps vacatedAt — allocations are never deleted.

One live allocation per student, enforced by a partial unique index on the database, so two concurrent allocations cannot both pass an application-level check. Attempting a second returns 409 telling the warden to use transfer instead.

A note on Mongoose 9

Mongoose 9 has dropped callback-style middleware. A hook written pre('validate', function (next) {...}) is silently skipped and next(err) throws next is not a function. Every hook here is an async function that throws. Worth knowing before adding hooks elsewhere in backend/models/, because the failure mode is silence rather than an error.

Pages / Components Added or Modified

  • backend/models/HostelRoom.js — new; bed array, derived occupancy, occupyBed/releaseBedFor
  • backend/models/RoomAllocation.js — new; partial unique index, close() for vacate/transfer
  • backend/controllers/hostelController.js — new; 13 handlers including the repair endpoint
  • backend/routes/hostelRoutes.js — new; mounted at /api/hostel
  • backend/server.js — two lines to register the router
  • frontend/src/pages/HostelRoom.jsx — new; boarder page at /hostel
  • frontend/src/components/teacher/HostelPanel.jsx — new; Dashboard tab
  • frontend/src/App.jsx — lazy import plus the /hostel route
  • frontend/src/pages/TeacherDashboard.jsx — one tab entry

Verification

  • node --check passes on every new backend file
  • npx eslint clean on the new frontend files
  • Model behaviour exercised directly against in-memory documents: bed auto-generation from capacity, occupancy and status flipping to full and back, double-booking a bed, allocating into a blocked bed or a maintenance room, shrinking capacity below the occupied count, growing capacity adding beds, releasing a bed for a non-occupant, closing an allocation twice, and closing with an invalid status — all reject with the intended message

Checklist

  • Lint passes with no new errors
  • Model guards exercised directly
  • Tested against a live MongoDB instance — no database credentials available to me, so the HTTP layer has not been run end to end
  • Responsive layout (room grid collapses on mobile; dialogs scroll within the viewport)
  • No new console errors or warnings
  • Screenshots — omitted, the pages need seeded rooms and allocations to show anything

Notes

Purely additive. The only shared-file changes are the router registration in server.js, the route in App.jsx and one tab entry in TeacherDashboard.jsx.

Adds a hostel module: rooms with a per-bed array, allocations that are
closed rather than deleted, and a warden dashboard.

The bed array is the single source of truth. `occupiedBeds`, `status`,
`bedsAvailable` and `occupancyRate` are all recomputed from it in one
pre-validate hook, so the counter and the beds cannot drift the way the
spreadsheet this replaces did. Beds are generated from capacity by the model,
and shrinking capacity below the occupied count is refused rather than
silently dropping a student.

Allocation and transfer save the room before the allocation row: if the second
write fails the bed is handed back, and the worst surviving case is a bed
marked occupied with no row, which the recompute endpoint repairs. The reverse
order would hand one bed to two students. A transfer closes the old row as
transferred and links it to the new one, so the history reads as a chain.

`maintenance` and `closed` are warden-set states and are never overwritten by
the derived available/full flip.

Closes Sitaram8472#263
@MOHITKOURAV01

Copy link
Copy Markdown
Contributor Author

Merge note for whoever lands these

This PR is one of five sibling feature PRs (#267#271). Each is independent in its own right — separate models, controllers, routers, pages and panels, with no shared logic — but all five register themselves in the same three files:

  • backend/server.js — one require and one app.use
  • frontend/src/App.jsx — one lazy import and one <Route>
  • frontend/src/pages/TeacherDashboard.jsx — one import, one tab entry, one render line

So the first of the five to merge will go in clean, and the remaining four will then conflict in exactly those three files. Nothing else conflicts.

I simulated the full sequential merge locally and verified the combined result. Two things worth passing on:

  1. The resolution is "keep both sides" in all cases — the additions are independent and order does not matter.

  2. App.jsx needs care. A naive keep-both resolution (or a union merge driver) silently produces invalid JSX: each route block ends with the same two lines

        </RoleProtectedRoute>
      } />

    which get treated as shared trailing context and kept only once, so the earlier routes lose their closing tags. The build then fails with Unexpected closing "Routes" tag does not match opening "RoleProtectedRoute" tag. Each <Route> needs its own closing pair. I hit this exactly while testing, so it is worth knowing before it looks like one of the PRs is broken.

Verified on the fully merged tree (all five together):

  • node --check backend/server.js passes; all five routers load and all nine new models register with no name collisions
  • npx vite build succeeds with all five pages code-split into their own chunks
  • npx eslint src/ reports 18 problems on the merged tree and 18 on main — these five modules add none

Happy to rebase and push the resolution on this branch as soon as the first sibling lands; just say which order you want them in.

Room search, boarder search and the block filter all passed raw query strings
to `new RegExp`, so the input was silently reinterpreted as a pattern.

A search of `.*` matched every room rather than rooms containing that text.
A term like `(a+)+$` triggered catastrophic backtracking — roughly two minutes
of pinned CPU for a 33-character query string, which any signed-in user could
send. An unbalanced `[` threw and surfaced as a 500 instead of a bad-request.

Metacharacters are now escaped through one helper so terms match literally,
and the term is capped at 80 characters. The block filter uses the same
escaping behind a prefix matcher.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Hostel Room Allocation & Occupancy tracking with bed-level accounting

1 participant