EP-002: Property & Room Management
Business Objective
A hotel's inventory is its core revenue asset. Accurate property configuration, room type definitions, and real-time room status tracking are foundational to every downstream operation -- reservations, housekeeping, front desk, and channel management. This epic ensures that hotel operators can onboard a property rapidly (target: under 2 hours for a 100-room hotel), manage room inventory with precision, and keep room status synchronized across all departments. Incorrect room status or stale inventory data directly translates to lost revenue (overbooking penalties, OTA parity violations, or unsold rooms).
Audience
- Primary: Admin, Manager (property setup and ongoing configuration)
- Secondary: Front Desk (room status view), Housekeeping (status updates), Reservations (availability dependency)
- Tertiary: Super Admin (multi-property chain oversight)
Scope
In Scope
- Property CRUD (create, read, update, soft-delete) with address, GSTIN, PAN, star rating, timezone, currency
- Room type CRUD with amenities list, photo gallery (JSON images array), bed type, max occupancy, base rate, extra adult/child rates, size (sqm)
- Room inventory CRUD -- create rooms with room number, floor, building, features (smoking, accessible, connecting), sort order
- Room status lifecycle with 5 housekeeping statuses (CLEAN, DIRTY, INSPECTED, PICKUP, OUT_OF_ORDER/OUT_OF_SERVICE)
- Room status summary endpoint (counts per status per property)
- Room notes management (front desk and housekeeping can annotate rooms)
- Room status change logging (audit trail with previous/new status, changed-by, reason)
- Rate plan CRUD with meal plan (EP/CP/MAP/AP), payment policy, min/max stay, date-level rates, restrictions (CTA, CTD, min LOS)
- Cancellation policy CRUD with rule-based charge calculation (days before arrival, charge percentage)
- Property onboarding wizard (guided setup with progress tracking)
- Data migration from legacy PMS (CSV/XML imports for eZee, Hotelogix, Opera; OTA channel re-mapping; reconciliation reports)
- Multi-building support (building field on rooms)
- Redis caching for property and room data
Out of Scope
- Floor plan visual editor (drag-and-drop room placement) -- deferred to Phase 3
- 3D room virtual tours -- future mobile app feature
- Dynamic pricing / revenue management engine -- covered in EP-012 (Revenue Management)
- Channel manager rate push -- covered in EP-009 (Channel Manager)
Current Implementation Status
- Backend (property-service): Fully built.
PropertiesControllerwith POST/GET/PUT/DELETE.@Roles('SUPER_ADMIN', 'ADMIN')for create/delete,'MANAGER'added for update.@CurrentTenant()decorator for tenant scoping.PaginationDtosupport. Redis cache layer in service. - Backend (room-types): Fully built.
RoomTypesControllerwith CRUD. Scoped to property viaGET /properties/:propertyId/room-types. Amenities stored asString[], images asJson. - Backend (rooms): Fully built.
RoomsControllerwith CRUD, status update (PATCH /rooms/:id/statuswith@CurrentUser()for audit), notes update (PATCH /rooms/:id/notes), room summary (GET /properties/:propertyId/rooms/summary). Filters by roomTypeId, status, floor, isOccupied. - Backend (rate-plans): Fully built.
RatePlansControllerwith CRUD, date-level rates (GET/PUT /rate-plans/:id/rates), bulk rate update, restrictions (POST/GET /rate-plans/:id/restrictions). - Backend (cancellation-policies): Fully built. CRUD with default policy endpoint (
GET /properties/:propertyId/cancellation-policies/default). - Backend (onboarding):
OnboardingControllerwithPOST /onboarding(start) andGET /onboarding/:propertyId/status. Roles restricted to SUPER_ADMIN, ADMIN. - Backend (migration): Extensive.
MigrationControllerwith CSV imports (rooms, guests, folios, POS menu), plus dedicated importers for eZee (CSV), Hotelogix (XML), Opera (CSV). Each supports guests, room-types, rooms, reservations, rate-plans. OTA re-mapping (detect,apply,getExistingChannels). Reconciliation report generation. - Dashboard: Settings pages for property, room types, rooms. Setup wizard with step tracker.
- Tests: Unit tests for property service, room status transitions.
Gap Analysis
- Floor Plan Visual Editor: No implementation. The
Roommodel hasfloorandbuildingfields but no spatial coordinates (x/y) for visual placement. Hotels with 200+ rooms need a visual map to quickly identify room locations. Impact: Medium -- workaround is list/grid view. - Room Photos Gallery UI: Backend stores images as JSON array on
RoomType, andUploadsControllergenerates pre-signed S3 URLs. Dashboard UI for uploading/reordering photos is incomplete (upload button exists but drag-to-reorder and bulk upload missing). Impact: Low -- photos can be managed via API. - Multi-Building Support: The
buildingfield exists on Room model and can be filtered, but there is no dedicated Building entity (no building-level settings, no building summary dashboard). Impact: Medium for resort properties with multiple wings. - Legacy PMS Migration -- Additional Formats: eZee, Hotelogix, and Opera importers are built. Missing: IDS Next (FX/Fortune), Protel, Clock PMS, and Guestline exports. Also missing: automated field mapping detection (currently requires manual column mapping). Impact: High for onboarding hotels migrating from less common PMS systems.
- Rate Calendar UI: Backend supports date-level rates and restrictions, but dashboard lacks a visual rate calendar grid (date x room-type matrix). Revenue managers need this for at-a-glance rate management. Impact: Medium.
User Stories
| Story ID | Title | Priority | Points | Status | Sprint |
|---|---|---|---|---|---|
| US-002-001 | Property CRUD | P0 | 5 | Built | 1 |
| US-002-002 | Room Type CRUD with Amenities & Photos | P0 | 5 | Built | 1 |
| US-002-003 | Room Inventory CRUD | P0 | 5 | Built | 1 |
| US-002-004 | Room Status Lifecycle Management | P0 | 5 | Built | 2 |
| US-002-005 | Room Status Summary Dashboard | P0 | 3 | Built | 2 |
| US-002-006 | Property Onboarding Wizard | P0 | 8 | Partial | 2 |
| US-002-007 | Rate Plan Configuration | P1 | 8 | Built | 3 |
| US-002-008 | Cancellation Policy Management | P1 | 5 | Built | 3 |
| US-002-009 | Date-Level Rate & Restriction Management | P1 | 5 | Built | 3 |
| US-002-010 | Room Features & Special Attributes | P1 | 3 | Built | 2 |
| US-002-011 | Legacy PMS Data Migration (eZee, Hotelogix, Opera) | P2 | 13 | Partial | 6 |
| US-002-012 | Multi-Building / Wing Support | P2 | 5 | Partial | 8 |
| US-002-013 | Floor Plan Visual Editor | P2 | 13 | Not Started | 12 |
| US-002-014 | Room Photo Gallery Management | P2 | 5 | Partial | 8 |
Dependencies
- EP-001 (Auth & Access Control): Property CRUD requires authenticated users with ADMIN/SUPER_ADMIN roles.
- EP-003 (Reservation Management): Reservations depend on room types, rooms, and rate plans existing.
- EP-009 (Channel Manager): Channel room/rate mappings reference room types and rate plans.
Acceptance Criteria (Epic Level)
- Admin can create a property with all India-specific fields (GSTIN, PAN, timezone Asia/Kolkata, currency INR)
- Room types can be configured with amenities, photos, occupancy limits, and base rates
- Rooms can be created, assigned to room types, and filtered by floor/status/type
- Room status can be updated by Front Desk and Housekeeping staff with full audit trail
- Rate plans with date-level pricing and restrictions can be created and bulk-updated
- Cancellation policies with rule-based charges are linked to rate plans
- Onboarding wizard guides a new property from zero to operational in under 2 hours
- Data can be imported from at least 3 legacy PMS systems (eZee, Hotelogix, Opera)
- Property and room data is cached in Redis with appropriate TTL and invalidation
KPIs / Success Metrics
- Property onboarding time: < 2 hours for 100-room hotel
- Room status update latency: < 200ms (cached reads)
- Room status accuracy: 99.5% (status matches physical room state within 15 minutes)
- Data migration success rate: > 95% record import without manual intervention
- Room type configuration completeness: > 90% of room types have photos, amenities, and rates configured
Detailed User Stories
US-002-001: Property CRUD
As a hotel admin, I want to create and manage property records with all operational details, So that the property is correctly configured in the system with accurate GSTIN, address, and settings for downstream operations.
Priority: P0 | Points: 5 | Sprint: 1 | Status: Built
Implementation Status
- Backend:
PropertiesController--POST /properties(SUPER_ADMIN, ADMIN),GET /properties(tenant-scoped via@CurrentTenant()),GET /properties/:id,PUT /properties/:id(SUPER_ADMIN, ADMIN, MANAGER),DELETE /properties/:id(SUPER_ADMIN, ADMIN, returns 204).CreatePropertyDtoandUpdatePropertyDtowith validation. Redis cache on reads. - Frontend: Settings > Property page with form fields for name, code, type (HOTEL/RESORT/SERVICE_APARTMENT/BOUTIQUE/MOTEL/HOSTEL), star rating, address, contact, GSTIN, PAN, timezone, currency.
Acceptance Criteria
Scenario: Create a new property with Indian tax details
Given the admin is logged in with ADMIN role
When they submit a new property with name "Grand Palace Hotel", code "GPH",
type "HOTEL", star rating 4, city "Mumbai", state "Maharashtra",
GSTIN "27AABCU9603R1ZX", PAN "AABCU9603R", timezone "Asia/Kolkata", currency "INR"
Then the property is created with a UUID and returned with status 201
And the property appears in GET /properties scoped to the admin's tenant
Scenario: Prevent duplicate property code within tenant
Given a property with code "GPH" already exists for the current tenant
When the admin attempts to create another property with code "GPH"
Then the system returns 400 with message "Property code already exists for this tenant"
Scenario: Update property GSTIN for GST compliance
Given the property "Grand Palace Hotel" exists with no GSTIN
When the manager updates the property with GSTIN "27AABCU9603R1ZX"
Then the property record is updated and the Redis cache is invalidated
And subsequent GET requests return the updated GSTIN
US-002-002: Room Type CRUD with Amenities & Photos
As a hotel manager, I want to define room types with detailed attributes including amenities, photos, and pricing, So that guests and booking channels have accurate room information for booking decisions.
Priority: P0 | Points: 5 | Sprint: 1 | Status: Built
Implementation Status
- Backend:
RoomTypesController-- full CRUD.POST /room-types,GET /properties/:propertyId/room-types,GET /room-types/:id,PUT /room-types/:id,DELETE /room-types/:id. Amenities asString[], images asJson(array of objects with url, caption, sortOrder). Bed type, max occupancy/adults/children, base rate, extra adult/child rate, size in sqm. - Frontend: Settings > Room Types page with form, amenity multi-select, image upload section.
Acceptance Criteria
Scenario: Create a Deluxe Room type with amenities and pricing
Given the manager navigates to Settings > Room Types
When they create a room type with code "DLX", name "Deluxe Room",
max occupancy 3, max adults 2, max children 1, bed type "King",
base rate INR 5500, extra adult rate INR 1200, extra child rate INR 600,
amenities ["WiFi", "AC", "Mini Bar", "Tea/Coffee Maker", "Safe Locker"],
size 32 sqm
Then the room type is created with status 201
And the room type appears in the property's room type list
Scenario: Upload room type photos
Given the Deluxe Room type exists
When the manager uploads 5 photos with captions
Then the images JSON array stores each photo with url, caption, and sortOrder
And the photos are retrievable via the room type GET endpoint
Scenario: Prevent deletion of room type with active rooms
Given the Deluxe Room type has 20 rooms assigned to it
When the manager attempts to delete the room type
Then the system returns 409 with message "Cannot delete room type with active rooms"
US-002-003: Room Inventory CRUD
As a hotel admin, I want to create and manage individual rooms assigned to room types, So that the property has an accurate room inventory for reservations and front desk operations.
Priority: P0 | Points: 5 | Sprint: 1 | Status: Built
Implementation Status
- Backend:
RoomsController--POST /rooms(SUPER_ADMIN, ADMIN, MANAGER),GET /properties/:propertyId/roomswith filters (roomTypeId, status, floor, isOccupied),GET /rooms/:id,PUT /rooms/:id,DELETE /rooms/:id(204). Room fields: roomNumber, floor, building, features (String[]), isSmoking, notes, sortOrder, isActive. - Frontend: Settings > Rooms page with data table, filters, create/edit dialogs.
Acceptance Criteria
Scenario: Create rooms on the third floor
Given the Deluxe Room type "DLX" exists for the property
When the admin creates rooms 301, 302, 303, 304, 305 on floor "3",
assigned to room type "DLX", with features ["balcony", "city-view"]
Then 5 rooms are created, each with default status VACANT_CLEAN and isOccupied false
And the room numbers are unique within the property
Scenario: Filter rooms by status and floor
Given the property has 100 rooms across 5 floors with various statuses
When the front desk agent filters rooms by status "VACANT_CLEAN" and floor "3"
Then only rooms on floor 3 with VACANT_CLEAN status are returned
And the response includes pagination metadata
Scenario: Mark a room as smoking-accessible
Given room 305 exists in the inventory
When the manager updates room 305 with isSmoking true and features ["accessible", "connecting-to-306"]
Then the room record reflects isSmoking true and updated features array
And the room appears in accessibility-filtered searches
US-002-004: Room Status Lifecycle Management
As a front desk agent or housekeeping supervisor, I want to update room status through its lifecycle (CLEAN, DIRTY, INSPECTED, OUT_OF_ORDER, OUT_OF_SERVICE), So that all departments have real-time visibility into which rooms are available, need cleaning, or are under maintenance.
Priority: P0 | Points: 5 | Sprint: 2 | Status: Built
Implementation Status
- Backend:
PATCH /rooms/:id/statuswithUpdateRoomStatusDto. Roles: SUPER_ADMIN, ADMIN, MANAGER, FRONT_DESK, HOUSEKEEPING.@CurrentUser()captures who changed the status.RoomStatusLogmodel stores previous/new status, changedBy, reason, timestamp. SeparateFrontOfficeStatus(VACANT, OCCUPIED, DUE_OUT, CHECKED_OUT) andHousekeepingStatus(CLEAN, DIRTY, INSPECTED, PICKUP) enums. - Frontend: Room grid in Front Desk module with color-coded status badges. Housekeeping floor view with status toggle.
- Events:
room.status.changedKafka event published on every status change.
Acceptance Criteria
Scenario: Housekeeping marks a room as clean after checkout cleaning
Given room 301 has housekeeping status DIRTY after guest checkout
When the housekeeping attendant updates room 301 status to CLEAN
Then the room status log records: previousStatus DIRTY, newStatus CLEAN,
changedBy "<attendant-user-id>", timestamp now
And a Kafka event "room.status.changed" is published with roomId and new status
And the room summary endpoint reflects one fewer DIRTY room and one more CLEAN room
Scenario: Supervisor inspects a cleaned room
Given room 301 has housekeeping status CLEAN
When the supervisor updates status to INSPECTED with reason "Passed inspection"
Then the status changes to INSPECTED and the front office status becomes VACANT
And the room becomes available for check-in assignment
Scenario: Place a room out of order for maintenance
Given room 412 has a plumbing issue
When the manager sets room 412 to OUT_OF_ORDER with reason "Bathroom leak - plumber scheduled"
Then room 412 is excluded from availability calculations
And the room appears with OUT_OF_ORDER status on the front desk room grid
And the status change is logged with the maintenance reason
US-002-005: Room Status Summary Dashboard
As a front desk manager, I want to see a summary of room statuses at a glance, So that I can quickly assess how many rooms are available, occupied, dirty, or out of order during shift handover.
Priority: P0 | Points: 3 | Sprint: 2 | Status: Built
Implementation Status
- Backend:
GET /properties/:propertyId/rooms/summaryreturns counts grouped by status (VACANT_CLEAN, VACANT_DIRTY, OCCUPIED, OUT_OF_ORDER, OUT_OF_SERVICE). Cached in Redis with short TTL, invalidated on status change. - Frontend: Dashboard widget showing room status pie chart and counts.
Acceptance Criteria
Scenario: View room status summary during morning shift
Given the property has 100 rooms: 40 OCCUPIED, 25 VACANT_CLEAN, 20 VACANT_DIRTY,
12 INSPECTED, 2 OUT_OF_ORDER, 1 OUT_OF_SERVICE
When the front desk manager calls GET /properties/:propertyId/rooms/summary
Then the response returns counts for each status category
And the total equals the property's active room count (100)
Scenario: Summary updates in real-time after status change
Given the room summary shows 25 VACANT_CLEAN rooms
When housekeeping marks one more room as CLEAN (was DIRTY)
Then the Redis cache is invalidated
And the next summary request shows 26 VACANT_CLEAN and 19 VACANT_DIRTY
US-002-006: Property Onboarding Wizard
As a hotel admin setting up a new property, I want a guided onboarding wizard that walks me through property details, room types, rooms, rate plans, and policies, So that I can get the property operational quickly without missing critical configuration steps.
Priority: P0 | Points: 8 | Sprint: 2 | Status: Partial
Implementation Status
- Backend:
OnboardingController--POST /onboardingstarts the workflow (SUPER_ADMIN, ADMIN),GET /onboarding/:propertyId/statusreturns progress with step completion flags. - Frontend: Setup wizard with step tracker in dashboard. Steps: Property Details, Room Types, Rooms, Rate Plans, Policies, Staff Users.
- Gap: Wizard does not auto-populate room numbers from a template (e.g., "Floor 1: 101-110, Floor 2: 201-210"). Tax configuration step (GST slab setup for different room tariffs) is missing. No "import from spreadsheet" option within the wizard flow.
Acceptance Criteria
Scenario: Complete onboarding wizard for a 50-room hotel
Given the admin starts the onboarding wizard for a new property
When they complete all steps: Property Details (name, address, GSTIN),
Room Types (3 types: Standard, Deluxe, Suite),
Rooms (50 rooms across 5 floors), Rate Plans (Rack Rate, Corporate Rate),
Cancellation Policies (Flexible, Non-Refundable), Staff Users (5 users)
Then the onboarding status shows 100% completion
And the property is marked as operational
And all configured entities are accessible via their respective APIs
Scenario: Resume partially completed onboarding
Given the admin completed Property Details and Room Types but stopped
When they return to the onboarding wizard
Then steps 1 and 2 show as completed with green checkmarks
And step 3 (Rooms) is highlighted as the current step
And previously entered data is preserved
Scenario: Validate mandatory fields before advancing steps
Given the admin is on the Property Details step
When they attempt to proceed without entering GSTIN
Then a validation warning shows "GSTIN is recommended for GST invoice generation"
And the admin can choose to skip (soft warning) or fill it in
US-002-007: Rate Plan Configuration
As a revenue manager, I want to create rate plans with meal plan inclusions, payment policies, and validity periods, So that the hotel can offer differentiated pricing for different market segments (corporate, OTA, direct).
Priority: P1 | Points: 8 | Sprint: 3 | Status: Built
Implementation Status
- Backend:
RatePlansController-- full CRUD. Fields: code, name, description, mealPlan (EP/CP/MAP/AP), inclusions (String[]), cancellationPolicyId (FK), paymentPolicy (PAY_NOW/PAY_AT_HOTEL/PARTIAL_PREPAYMENT), minStay, maxStay, minAdvanceBooking, maxAdvanceBooking, validFrom, validTo, isRefundable. Linked to cancellation policies. Date-level rates viaRatePlanRatemodel. Restrictions endpoint for CTA/CTD/min-LOS. - Frontend: Settings > Rate Plans page with form including meal plan dropdown, policy association, validity date picker.
Acceptance Criteria
Scenario: Create a corporate rate plan with CP meal plan
Given the property has room types and a flexible cancellation policy configured
When the revenue manager creates a rate plan with code "CORP", name "Corporate Rate",
meal plan CP (Continental Plan - Breakfast included), payment policy PAY_AT_HOTEL,
minimum stay 1 night, cancellation policy "Flexible", valid from 2026-04-01 to 2027-03-31
Then the rate plan is created and linked to the cancellation policy
And the rate plan is available for reservation creation
Scenario: Bulk update date-level rates for peak season
Given the "Rack Rate" rate plan exists for Deluxe Room type
When the revenue manager bulk updates rates via PUT /rate-plans/:id/rates
with dates 2026-12-20 to 2026-01-05, rate INR 8500 (up from INR 5500),
extra adult INR 1500
Then 17 date-level rate records are created/updated
And availability checks for those dates reflect the peak season pricing
Scenario: Set minimum length of stay restriction for Diwali week
Given the Rack Rate plan exists
When the revenue manager sets restrictions for 2026-10-20 to 2026-10-26
with minLOS 3, closedToArrival false, closedToDeparture false
Then bookings for those dates require a minimum 3-night stay
And availability responses include the restriction metadata
US-002-008: Cancellation Policy Management
As a hotel admin, I want to define cancellation policies with tiered charge rules, So that the hotel is protected against last-minute cancellations while offering flexibility for advance bookings.
Priority: P1 | Points: 5 | Sprint: 3 | Status: Built
Implementation Status
- Backend:
CancellationPoliciesController-- full CRUD.rulesstored as JSON array:[{ daysBeforeArrival, chargeType, chargeValue }]. Default policy per property viaGET /properties/:propertyId/cancellation-policies/default. isDefault flag. - Frontend: Settings > Cancellation Policies page with rule builder.
Acceptance Criteria
Scenario: Create a flexible cancellation policy
Given the admin navigates to Settings > Cancellation Policies
When they create a policy named "Flexible" with rules:
- Cancel 7+ days before arrival: 0% charge
- Cancel 3-7 days before arrival: 50% of first night
- Cancel 0-3 days before arrival: 100% of first night
Then the policy is created and available for linking to rate plans
Scenario: Set default cancellation policy for a property
Given "Flexible" and "Non-Refundable" policies exist
When the admin marks "Flexible" as the default policy
Then new rate plans without an explicit cancellation policy inherit "Flexible"
And GET /properties/:propertyId/cancellation-policies/default returns "Flexible"
Scenario: Prevent deletion of policy linked to active rate plans
Given the "Flexible" policy is linked to 3 active rate plans
When the admin attempts to delete the "Flexible" policy
Then the system returns 409 with message "Cannot delete policy linked to active rate plans"
US-002-009: Date-Level Rate & Restriction Management
As a revenue manager, I want to set day-by-day rates and booking restrictions per rate plan and room type, So that pricing responds to demand patterns (weekends, festivals, events) and inventory is controlled.
Priority: P1 | Points: 5 | Sprint: 3 | Status: Built
Implementation Status
- Backend:
RatePlanRatemodel with composite unique key (ratePlanId + roomTypeId + date).GET /rate-plans/:id/rates?startDate=&endDate=&roomTypeId=,PUT /rate-plans/:id/rates(bulk upsert).POST /rate-plans/:id/restrictionsandGET /rate-plans/:id/restrictions?startDate=&endDate=for CTA/CTD/minLOS. - Frontend: Rate grid UI exists but lacks visual calendar-style interface.
Acceptance Criteria
Scenario: View rate calendar for a 30-day period
Given the Rack Rate plan has date-level rates configured for April 2026
When the revenue manager calls GET /rate-plans/:id/rates
with startDate "2026-04-01" and endDate "2026-04-30" and roomTypeId for Deluxe
Then the response returns 30 daily rate entries with rate, extraAdult, extraChild amounts
Scenario: Apply weekend surcharge
Given the Rack Rate has a base rate of INR 5500 for Standard rooms
When the revenue manager bulk updates Saturday and Sunday rates to INR 6500
for the next 3 months (approximately 26 weekend dates)
Then all specified dates are updated via a single PUT request
And the availability engine returns the correct rate based on check-in date
Scenario: Close arrivals during a sold-out conference period
Given a major conference is expected from March 10-12, 2026
When the revenue manager sets closedToArrival true for March 10-12 on the OTA rate plan
Then no new bookings can arrive on those dates via OTA channels
And existing confirmed reservations for those dates are unaffected
US-002-010: Room Features & Special Attributes
As a reservations agent, I want to tag rooms with special features (smoking, accessible, connecting, sea-view, pool-facing), So that I can match guest preferences to the right room during assignment.
Priority: P1 | Points: 3 | Sprint: 2 | Status: Built
Implementation Status
- Backend:
Roommodel hasisSmoking(Boolean),features(String[] -- flexible tags),building(String),floor(String). Filter support onGET /properties/:propertyId/rooms. - Frontend: Room edit form has smoking toggle and feature tag input.
Acceptance Criteria
Scenario: Tag rooms with accessibility features
Given rooms 101 and 102 are wheelchair-accessible with grab bars
When the admin updates these rooms with features ["accessible", "grab-bars", "roll-in-shower"]
Then the features are stored as a String array on each room record
And front desk can filter rooms by feature to assign accessible rooms to guests who need them
Scenario: Identify connecting rooms for family bookings
Given rooms 305 and 306 have a connecting door
When the admin tags room 305 with features ["connecting-to-306"]
and room 306 with features ["connecting-to-305"]
Then the reservations agent can identify connecting rooms for families booking multiple rooms
US-002-011: Legacy PMS Data Migration (eZee, Hotelogix, Opera)
As a hotel admin migrating from an existing PMS, I want to import historical data (guests, rooms, room types, reservations, rate plans) from my legacy system, So that we retain guest history and operational continuity when switching to HMP.
Priority: P2 | Points: 13 | Sprint: 6 | Status: Partial
Implementation Status
- Backend:
MigrationControllerwith comprehensive import endpoints:- Legacy CSV:
POST /migration/:propertyId/rooms,/guests,/folios,/pos-menu - eZee CSV:
POST /migration/import/ezee/{guests,room-types,rooms,reservations,rate-plans} - Hotelogix XML:
POST /migration/import/hotelogix/{guests,room-types,rooms,reservations,rate-plans} - Opera CSV:
POST /migration/import/opera/{guests,room-types,rooms,reservations,rate-plans} - OTA Re-mapping:
POST /migration/ota-remap/detect,/ota-remap/:propertyId/apply - Reconciliation:
POST /migration/reconciliation/:propertyId - Services:
EzeeImporterService,HotelogixImporterService,OperaImporterService,OtaRemapperService,ReconciliationService
- Legacy CSV:
- Gap: No dashboard UI for migration (API-only). No support for IDS Next (FX/Fortune), Protel, Clock PMS formats. No automated field mapping -- admin must prepare CSV in expected format. No progress tracking for large imports.
Acceptance Criteria
Scenario: Import guest data from eZee CSV export
Given the admin has exported 2,500 guest records from eZee as CSV
When they POST the CSV data to /migration/import/ezee/guests with the propertyId
Then guests are imported with name, email, phone, nationality mapped correctly
And the response includes counts: imported 2,450, skipped 30 (duplicates), errors 20 (malformed)
And a reconciliation report confirms source count matches within 2% tolerance
Scenario: Migrate rooms and room types from Opera
Given the admin has Opera CSV exports for room types and rooms
When they import room types first, then rooms (linking to imported room types)
Then room types are created with amenities and rates mapped from Opera fields
And rooms are created with correct room numbers, floors, and room type associations
Scenario: Auto-detect and remap OTA channel sources
Given the imported reservations have source values like "BDC", "EXP", "AGD"
When the admin calls POST /migration/ota-remap/detect with those source values
Then the system returns detected mappings: "BDC" -> BOOKING_COM, "EXP" -> EXPEDIA, "AGD" -> AGODA
And the admin can apply these mappings to normalize booking sources
US-002-012: Multi-Building / Wing Support
As a resort manager with multiple buildings/wings, I want to organize rooms by building, So that guests can be assigned to their preferred building and housekeeping routes are optimized per wing.
Priority: P2 | Points: 5 | Sprint: 8 | Status: Partial
Implementation Status
- Backend:
Roommodel hasbuildingfield (String, nullable). Rooms can be filtered by building in the list endpoint. No dedicatedBuildingentity. - Gap: No building-level configuration (name, description, floor count, amenities per building). No building summary counts. No building-based housekeeping assignment optimization.
Acceptance Criteria
Scenario: Organize rooms by building for a resort
Given "Lakeside Resort" has three buildings: Main Block, Pool Wing, Garden Cottage
When the admin creates rooms with building field set to "Main Block", "Pool Wing", or "Garden Cottage"
Then rooms can be filtered by building via GET /properties/:propertyId/rooms?building=Pool Wing
And the front desk can assign rooms in the guest's preferred building
Scenario: View room summary by building
Given rooms are tagged with building names
When the manager requests room summary filtered by building "Main Block"
Then the summary shows status counts only for rooms in Main Block
And the total matches the number of rooms in that building
US-002-013: Floor Plan Visual Editor
As a hotel manager, I want to create a visual floor plan showing room positions, corridors, and facilities, So that the front desk can visually select rooms during check-in and housekeeping can plan efficient cleaning routes.
Priority: P2 | Points: 13 | Sprint: 12 | Status: Not Started
Implementation Status
- Backend: Not implemented. No spatial coordinates on Room model. No floor plan storage.
- Frontend: Not implemented.
- Note: The POS module's
POSTablemodel hasposX/posYfields for table placement, providing a pattern for room positioning.
Acceptance Criteria
Scenario: Design a floor plan for the third floor
Given the admin opens the Floor Plan Editor for Floor 3
When they drag room boxes onto a canvas, position them along corridors,
and add landmarks (elevator, staircase, housekeeping closet)
Then the floor plan is saved with room positions (x, y coordinates)
And the front desk can click on a room in the visual plan to view its status and guest info
Scenario: Live room status on floor plan
Given the floor plan for Floor 3 is configured with 20 rooms
When the front desk views the floor plan
Then each room is color-coded: green (VACANT_CLEAN), red (OCCUPIED),
yellow (DIRTY), blue (INSPECTED), grey (OUT_OF_ORDER)
And clicking a room shows a popup with guest name (if occupied), status, and quick actions
US-002-014: Room Photo Gallery Management
As a hotel marketing manager, I want to upload, reorder, and manage photos for each room type, So that the booking engine and OTA channels display attractive, accurate room images.
Priority: P2 | Points: 5 | Sprint: 8 | Status: Partial
Implementation Status
- Backend:
RoomType.imagesstored as JSON array.UploadsControllergenerates pre-signed S3 URLs viaPOST /uploads/presigned-url(accepts fileName, contentType, folder). - Frontend: Photo upload button exists on room type edit page. Missing: drag-to-reorder, bulk upload, image cropping, caption editing inline.
- Gap: No image compression/resizing on upload. No automatic thumbnail generation for different display contexts (listing vs. detail vs. OTA feed).
Acceptance Criteria
Scenario: Upload multiple photos for a room type
Given the manager is editing the "Deluxe Room" room type
When they upload 8 photos (bedroom, bathroom, view, amenities, etc.)
Then each photo is uploaded to S3 via pre-signed URL
And the images JSON array on the room type contains 8 entries with url, caption, and sortOrder
Scenario: Reorder photos to set hero image
Given the Deluxe Room type has 8 photos uploaded
When the manager drags the "City View" photo to position 1
Then the sortOrder values are updated so "City View" has sortOrder 0
And the booking engine displays "City View" as the primary image
Scenario: Delete an outdated photo
Given the Deluxe Room was recently renovated
When the manager deletes the old bathroom photo from the gallery
Then the image entry is removed from the JSON array
And the S3 object is marked for cleanup