# Room Allocation & Guest Logic – Context Document

Use this document as context when discussing the room allocation / guest-only feature with ChatGPT or other AI. It describes current state, proposed changes, DB models, and open questions.

---

## 1. Current Architecture

### Overview
- **Stack:** React (CRA) frontend, Express backend, MongoDB (Mongoose). JWT auth.
- **Feature:** Admin allocates participants (and guests) to room units by date. Grid shows rooms (rows) × dates (columns). Cells show price when empty; when booked, show participant name, guest count, type, price.

### Relevant Components / Files

| Layer | File | Role |
|-------|------|------|
| Backend | `dashboard/models/Booking.js` | Booking schema (room_unit, date, participant_id, guest_count, allocation_type, price) |
| Backend | `dashboard/models/Room.js` | Room type: bed_count, no_of_rooms, price, hotel_id |
| Backend | `dashboard/models/RoomUnit.js` | One bookable unit per Room; room_label, room_id, hotel_id |
| Backend | `dashboard/Routes/BookingRoutes.js` | GET/POST/PATCH/DELETE bookings; `sumGuests()` for bed limit |
| Backend | `dashboard/Routes/ParticipantRoutes.js` | GET participants; `?slim=1` trims person_data (guest list) |
| Frontend | `src/components/ems/RoomsInformation.js` | Grid, drag-to-select dates, opens AllocationModal / BookingCellMenu |
| Frontend | `src/components/ems/AllocationModal.js` | Pick participant, allocation type, guest count; POST booking per date |
| Frontend | `src/components/ems/BookingCellMenu.js` | List bookings in cell; edit/delete; no “Add another” yet |
| Frontend | `src/utils/laDateUtils.js` | LA date keys (YYYY-MM-DD), todayLAKey, validation |

### Current Data Flow
1. **Load grid:** RoomsInformation → GET `/ems/room-bookings?room_id=&from_date=&to_date=` → list of bookings; grouped by `(room_unit_id, la_date_key)` into `bookingsMap`.
2. **Allocate:** User drags dates on empty cell → AllocationModal opens → GET `/ems/participants?include_org=true&slim=1` → user picks participant, sets guest_count, allocation_type → POST `/ems/room-bookings` per date (room_unit_id, date, participant_id, guest_count, allocation_type, price).
3. **Edit/cancel:** Click booked cell → BookingCellMenu with all bookings for that cell → PATCH/DELETE `/ems/room-bookings/:id`.

### Current Behaviour
- **guest_count:** Backend treats it as **total people** in the booking. Validation: `sum(guest_count) <= bed_count` per (room_unit, date). Min 1 in schema and UI.
- **Multiple bookings per cell:** Allowed (unique on `room_unit_id + la_date_key + participant_id`). Cell shows **first** booking’s name/type; “Guests” = sum of all bookings’ guest_count. Click opens menu with full list.
- **Participant guest list:** Stored in `Participant.person_data` (e.g. `dataGrid` / `guestDetails.dataGrid`). AllocationModal already shows “Guests registered for [name]” from slim API. Not yet used for “guest only” or per-booking guest selection.

---

## 2. Required / Proposed Architecture

### Changes Summary
- **Booking model:** Add `is_guest_only` (Boolean, default false). Change `guest_count` to mean “guests only” (min 0, default 0).
- **Occupancy:** One formula everywhere: per booking `personCount = is_guest_only ? guest_count : 1 + guest_count`; reject when sum(personCount) > bed_count.
- **API:** POST/PATCH accept and persist `is_guest_only`. Backend uses `sumOccupants()` (replacing `sumGuests`) with the new formula.
- **AllocationModal:** Checkbox “Guest only – participant not in this room”; send `is_guest_only`; allow guest_count 0 when not guest-only.
- **BookingCellMenu:** Show/edit `is_guest_only`; allow guest_count 0; “Add another” when beds left > 0 (opens AllocationModal for same unit+date).
- **Grid:** Occupancy display uses new formula; when multiple bookings per cell, show one at a time with “>” / “<” and sliding animation to cycle; click cell opens full menu.

### New / Modified Pieces
- **Booking schema:** New field `is_guest_only`; relax `guest_count` to min 0, default 0.
- **BookingRoutes:** New `sumOccupants(roomUnitId, laDateKey, excludeBookingId)`; POST/PATCH validate and persist `is_guest_only` and new occupancy.
- **AllocationModal:** State `isGuestOnly`, checkbox, validation for guest-only + guest_count ≥ 1.
- **BookingCellMenu:** Edit `is_guest_only`, optional “<”/“>” for multiple; “Add another” + callback to open AllocationModal.
- **RoomsInformation:** Pass full `cellBookings` into BookedCell; occupancy = sum of (is_guest_only ? guest_count : 1 + guest_count); BookedCell with index state + “>”/“<” and slide animation.

### Updated Data Flow
- Create/update booking: payload includes `is_guest_only`. Backend computes `personCount` and checks `currentOccupants + personCount <= bed_count`.
- Grid: For each cell, `totalOccupants` = sum over bookings of (is_guest_only ? guest_count : 1 + guest_count). Display “Occupants: X”. If multiple bookings, BookedCell shows one booking at a time; “>” cycles with slide; click opens BookingCellMenu.

---

## 3. Feature Goal

- **Semantics:** `guest_count` = number of **guests only**. Total people = 1 (participant) + guest_count when participant is in room; for “guest only” bookings, total = guest_count only (participant not in room but still linked via `participant_id`).
- **Edge case:** Participant + 1 guest in Room A; other 2 guests in Room B with no participant in Room B. Room B has one (or more) bookings with same participant_id, `is_guest_only: true`, and guest_count = 2 (or two bookings of 1 each). Occupancy in Room B = 2, not 3.
- **UX:** Admin can mark “Guest only” in AllocationModal; grid and menu show this; multiple bookings per cell are navigable with “>”/“<” and sliding animation; “Add another” from cell menu when capacity allows.

---

## 4. Assumptions

- `participant_id` on Booking is **always set** (never null). Guest-only is indicated only by `is_guest_only: true`.
- No data migration: existing bookings can be treated as non–guest-only (is_guest_only false); guest_count semantics change (old “total” vs new “guests only”) may require product decision for legacy data.
- Date keys are LA timezone (YYYY-MM-DD); `la_date_key` and `date` (UTC) are both stored on Booking.
- Unique constraint remains `(room_unit_id, la_date_key, participant_id)` — one booking per participant per room unit per day; multiple participants (and guest-only bookings) can share the same cell.
- Participant guest list stays in `person_data` (Formio/form); no separate Guest collection for room allocation. Optional future: store selected guest names on Booking (e.g. `guests` array) — not in current scope.

---

## 5. Open Questions

- **Legacy data:** After changing guest_count to “guests only”, how to interpret existing documents where guest_count was “total people”? (e.g. treat as 1 participant + (guest_count - 1) guests, or leave as-is and only apply new rules to new bookings?)
- **Concurrency:** Under high concurrency, two requests could both pass occupancy check then double-book. Is an atomic update or advisory lock required, or is single-admin usage acceptable?
- **Optional “guests” array on Booking:** Plan mentions optional `guests` array for picking specific guests by name. Confirm whether to add in this phase or defer.

---

## 6. Database Models (Relevant Collections)

### Booking (`dashboard/models/Booking.js`)
```javascript
{
  room_unit_id: ObjectId (ref RoomUnit), required
  date: Date, required
  la_date_key: String, required
  participant_id: ObjectId (ref Person), required
  guest_count: Number, default 1, min 1   // PROPOSED: default 0, min 0
  allocation_type: String, enum ['required','standard','private','athlete'], required
  price: Number, default null
  competition_id: ObjectId (ref Competition)
  hotel_id: ObjectId (ref Hotel)
  // PROPOSED: is_guest_only: Boolean, default false
}
// Indexes: { room_unit_id, la_date_key }, { room_unit_id, la_date_key, participant_id } unique
```

### Room (`dashboard/models/Room.js`)
```javascript
{
  hotel_id: [ObjectId] (ref Hotel), required
  competition_id: [ObjectId] (ref Competition)
  room_name: String
  room_type: String
  price: Number, default 0, min 0
  no_of_rooms: Number, required, min 1
  bed_count: Number, default 1, min 1   // capacity per room unit
  amenities: [String], default []
  from_date, to_date: Date
  from_la_date_key, to_la_date_key: String
  createdAt, updatedAt: Date
}
```

### RoomUnit (`dashboard/models/RoomUnit.js`)
```javascript
{
  room_id: ObjectId (ref Room), required
  hotel_id: ObjectId (ref Hotel), required
  room_label: String, required, trim
}
// timestamps
```

### Hotel (`dashboard/models/Hotel.js`)
```javascript
{
  global_hotel_id: ObjectId (ref GlobalHotels), required
  competition_id: ObjectId (ref Competition)
  competition_specific_contact_person, _email, _phone, _notes: String
}
// timestamps
```

### GlobalHotels (`dashboard/models/GlobalHotels.js`)
```javascript
{
  global_hotel_id: String, required, unique
  global_hotel_name: String, required
  global_address: String, required
  photo_url, contact_*, website_url, description, managed_by, ...
}
// collection: globalhotels
```

### Participant (`dashboard/models/Participant.js`)
```javascript
{
  person_id: ObjectId (ref Person)
  competition_id: [ObjectId] (ref Competition)
  group_id: ObjectId (ref Group)
  person_data: Object, default {}   // includes guest list: dataGrid / guestDetails.dataGrid / guest.dataGrid
  registration_status, approval_status: String
  is_deleted: Boolean, default false
  progress_breakdown: Map
}
// timestamps
```

### Person (`dashboard/models/Person.js`)
```javascript
{
  name, lname, email, ...  // Booking populates participant_id with Person (name, lname, email used in UI)
}
// collection: people
```

---

## 7. Occupancy Formula (Single Source of Truth)

```
per booking:  personCount = is_guest_only ? guest_count : 1 + guest_count
beds_occupied = sum(personCount) for all bookings with same (room_unit_id, la_date_key)
beds_left     = room.bed_count - beds_occupied
```
Validation: reject create/update if `beds_occupied + new_booking_personCount > bed_count`.

---

## 8. Reference Plans

- Implementation plan: `.cursor/plans/room_allocation_implementation_0dddfd98.plan.md`
- Handoff spec: `.cursor/plans/room_allocation_handoff_spec_1d46a57c.plan.md`
