Hotel Management Module - Data Models and Database Schema¶
Overview¶
This document describes the data models, entity classes, and database schema for the Hotel Management module. The system uses Java entity classes that map to relational database tables through a custom ORM layer (EntityFacade).
Entity Overview¶
The Hotel Management module consists of 4 primary entities:
| Entity | Table (Inferred) | Purpose | Lines of Code |
|---|---|---|---|
| CHotel | HOTEL | Hotel master data | 231 |
| CHotelRoom | HOTEL_ROOM | Room type definitions | 120 |
| CRoomCalendarEntry | ROOM_CALENDAR_ENTRY | Daily pricing entries | 463 |
| CMealPlan | MEAL_PLAN | Meal plan definitions | 77 |
Entity Relationship Diagram¶
┌──────────────────────────────────────────────────┐
│ MEAL_PLAN │
│ │
│ PK: planId │
│ plan (RO, BB, HB, FB, AI, etc.) │
│ name, planDesc, planOrder │
│ adultCost, childCost │
└──────────────────────────────────────────────────┘
↑
│ Referenced by mealBase
│ Referenced in mealSupplements JSON
│
┌──────────────────────────────────────────────────┐
│ HOTEL │
│ │
│ PK: hotelId │
│ UK: shDes (hotel code) │
│ name, shortName, hotelDesc │
│ country, area, city, stars │
│ direct, indirect (contract types) │
│ infant, child (age thresholds) │
│ chkIn, chkOut (times) │
│ fees, feesDesc, terms │
│ release (cancellation days) │
│ reservationContact │
│ productId (FK to PRODUCT - optional) │
└──────────────────────────────────────────────────┘
│
│ 1
│
│ N
↓
┌──────────────────────────────────────────────────┐
│ HOTEL_ROOM │
│ │
│ PK: roomId │
│ FK: hotelId → HOTEL.hotelId │
│ name, roomDesc │
│ maxOccupancy, maxAdults │
│ extraBed, extraBedRequired │
│ bedding, notes │
└──────────────────────────────────────────────────┘
│
│ 1
│
│ N
↓
┌──────────────────────────────────────────────────┐
│ ROOM_CALENDAR_ENTRY │
│ │
│ PK: calendarEntryId │
│ FK: roomId → HOTEL_ROOM.roomId │
│ UK: (roomId, market, stayDate) │
│ market (UAE/GCC/ROW) │
│ stayDate (check-in date) │
│ bookFrom, bookTo (booking window) │
│ promo, vendorId │
│ baseRate, specDayRate, specialDay │
│ sdRateType (ABS/ADD/PCT/PCO) │
│ adultRate, adultRateType │
│ childRate, childRateType │
│ adultSdRate, childSdRate (special day) │
│ extraBedRate │
│ rateBase (SGL/DBL/TRPL/QPL) │
│ mealBase (FK to MEAL_PLAN.planId) │
│ mealSupplements (JSON text) │
│ stopSale, available, onRequest │
│ mlos, sdmlos (min length of stay) │
│ mlosDesc, notes │
│ │
│ Transient fields (not stored): │
│ breakdown (String[]) │
│ calculatedCost (Double) │
└──────────────────────────────────────────────────┘
Entity Details¶
1. CHotel - Hotel Master Entity¶
File: tqapp/src/main/java/com/perun/tlinq/entity/hotel/CHotel.java
Purpose: Stores hotel master data including property details, contract information, check-in/out policies, and age thresholds.
Attributes¶
| Attribute | Type | Nullable | Description | Example | Database Column |
|---|---|---|---|---|---|
| hotelId | Integer | No (PK) | Primary key, auto-generated | 1042 | HOTEL_ID |
| shDes | String(10) | No (UK) | Unique hotel code | "ATLJMB" | SH_DES |
| name | String(200) | No | Full official hotel name | "Atlantis The Palm" | NAME |
| shortName | String(50) | Yes | Abbreviated display name | "Atlantis Palm" | SHORT_NAME |
| hotelDesc | String(2000) | Yes | Marketing description | "Luxury resort on Palm..." | HOTEL_DESC |
| productId | Integer | Yes | Link to product catalog | 2045 | PRODUCT_ID |
| direct | Boolean | Yes | Direct contract flag | true | DIRECT |
| indirect | Boolean | Yes | Indirect/DMC contract | false | INDIRECT |
| country | String(3) | Yes | ISO country code | "UAE" | COUNTRY |
| stars | Integer | Yes | Star rating (1-5) | 5 | STARS |
| infant | Integer | Yes | Infant max age (years) | 2 | INFANT |
| child | Integer | Yes | Child max age (years) | 12 | CHILD |
| chkIn | String(5) | Yes | Check-in time (HH:MM) | "15:00" | CHK_IN |
| chkOut | String(5) | Yes | Check-out time (HH:MM) | "12:00" | CHK_OUT |
| fees | Double | Yes | Hotel fees amount | 50.00 | FEES |
| feesDesc | String(500) | Yes | Description of fees | "City tax, service..." | FEES_DESC |
| terms | String(2000) | Yes | Terms and conditions | "Cancellation 72h..." | TERMS |
| area | String(3) | Yes | Area/Emirate code | "DXB" | AREA |
| city | String(50) | Yes | City name | "Dubai" | CITY |
| release | Integer | Yes | Release days (cancellation) | 7 | RELEASE |
| reservationContact | String(200) | Yes | Email addresses | "reservations@hotel.com" | RESERVATION_CONTACT |
Area Codes¶
Common area codes for UAE: - DXB: Dubai - ADB: Abu Dhabi - SHJ: Sharjah - AJM: Ajman - RAK: Ras Al Khaimah - FUJ: Fujairah - UAQ: Umm Al Quwain
Business Rules¶
- Unique Code:
shDesmust be unique across all hotels - Contract Type: At least one of
directorindirectmust be true - Star Rating: If provided, must be 1-5
- Age Thresholds:
- Default infant age: 2
- Default child age: 12
- Infant < Child always
- Times: Check-in time typically later than check-out time
- Release Days: Number of days before check-in that free cancellation is allowed
build() Method Logic¶
The build() method in CHotel.java:231 constructs an entity from a Map (JSON deserialization):
public CHotel build(Map ent) {
this.hotelId = (ent.containsKey("hotelId") ?
TypeUtil.extractInteger(ent.get("hotelId")) : null);
this.shDes = (String)ent.get("shDes");
this.name = (String) ent.get("name");
this.shortName = (String) ent.get("shortName");
// ... (continues for all 22 attributes)
return this;
}
Key Features:
- Null-safe extraction using TypeUtil helper methods
- Handles type conversions (String to Integer, String to Double, Object to Boolean)
- Returns this for method chaining
- Used by API layer when creating/updating hotels
2. CHotelRoom - Room Type Entity¶
File: tqapp/src/main/java/com/perun/tlinq/entity/hotel/CHotelRoom.java
Purpose: Defines room types for a hotel, including occupancy limits and bedding configuration.
Attributes¶
| Attribute | Type | Nullable | Description | Example | Database Column |
|---|---|---|---|---|---|
| roomId | Integer | No (PK) | Primary key, auto-generated | 5012 | ROOM_ID |
| hotelId | Integer | No (FK) | Foreign key to Hotel | 1042 | HOTEL_ID |
| name | String(100) | No | Room type name | "Deluxe Ocean View" | NAME |
| roomDesc | String(1000) | Yes | Room description | "35 sqm, king bed..." | ROOM_DESC |
| maxOccupancy | Integer | No | Maximum total persons | 3 | MAX_OCCUPANCY |
| maxAdults | Integer | No | Maximum adults | 2 | MAX_ADULTS |
| extraBed | Boolean | No | Extra bed available | true | EXTRA_BED |
| extraBedRequired | Boolean | No | Extra bed mandatory for 3rd adult | true | EXTRA_BED_REQUIRED |
| bedding | String(100) | Yes | Bedding description | "1 King or 2 Twin" | BEDDING |
| notes | String(500) | Yes | Room notes/restrictions | "No smoking" | NOTES |
Occupancy Logic¶
The relationship between occupancy fields:
maxOccupancy = Maximum total persons (adults + children)
maxAdults = Maximum adults allowed
maxChildren = maxOccupancy - maxAdults (implicit)
Examples:
1. maxOccupancy=2, maxAdults=2 → 2 adults only
2. maxOccupancy=3, maxAdults=2 → 2 adults + 1 child
3. maxOccupancy=4, maxAdults=2 → 2 adults + 2 children
4. maxOccupancy=3, maxAdults=3 → 3 adults (needs extra bed)
Extra Bed Logic¶
If extraBed = true AND extraBedRequired = true:
- 3rd adult requires extra bed
- Extra bed charge applies
If extraBed = true AND extraBedRequired = false:
- Extra bed optional (room has sofa bed/daybed)
- Extra bed charge applies only if requested
If extraBed = false:
- No extra bed available
- Occupancy strictly limited
Business Rules¶
- Occupancy Constraints:
maxAdults≤maxOccupancymaxOccupancy≥ 1-
maxAdults≥ 1 -
Room Naming: Should be unique within a hotel (not enforced at DB level)
-
Extra Bed:
- If
extraBedRequired = true, thenextraBedmust be true - Extra bed typically increases max occupancy by 1
build() Method Logic¶
public CHotelRoom build(Map ent) {
this.roomId = (ent.containsKey("roomId") ?
TypeUtil.extractInteger(ent.get("roomId")) : null);
this.hotelId = TypeUtil.extractInteger(ent.get("hotelId"));
this.name = (String)ent.get("name");
// ... (continues for all 10 attributes)
return this;
}
3. CRoomCalendarEntry - Rate Calendar Entity¶
File: tqapp/src/main/java/com/perun/tlinq/entity/hotel/CRoomCalendarEntry.java
Purpose: Stores daily pricing for room types by market segment. This is the most complex entity with 31 attributes handling sophisticated pricing logic.
Attributes¶
Identification Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| calendarEntryId | Integer | No (PK) | Primary key | CALENDAR_ENTRY_ID |
| roomId | Integer | No (FK) | Foreign key to Room | ROOM_ID |
| market | String(3) | No (UK) | Market segment (UAE/GCC/ROW) | MARKET |
| stayDate | Date | No (UK) | Check-in date for this rate | STAY_DATE |
Unique Constraint: (roomId, market, stayDate) must be unique
Booking Window Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| bookFrom | Date | Yes | Earliest booking date | BOOK_FROM |
| bookTo | Date | Yes | Latest booking date | BOOK_TO |
Logic: Room can only be booked if current date is between bookFrom and bookTo
Vendor and Promotion Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| vendorId | Integer | Yes | Vendor/partner ID | VENDOR_ID |
| promo | String(20) | Yes | Promotional code | PROMO |
Base Rate Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| baseRate | Double | No | Base room rate (normal day) | BASE_RATE |
| rateBase | String(4) | No | Rate basis (SGL/DBL/TRPL/QPL) | RATE_BASE |
Rate Base Options: - SGL: Single occupancy (1 person) - DBL: Double occupancy (2 persons) - TRPL: Triple occupancy (3 persons) - QPL: Quadruple occupancy (4 persons)
Special Day Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| specialDay | Boolean | No | Is this a special day (weekend/holiday)? | SPECIAL_DAY |
| specDayRate | Double | Yes | Special day rate (overrides baseRate) | SPEC_DAY_RATE |
| sdRateType | String(3) | Yes | Special day rate calculation type | SD_RATE_TYPE |
Special Day Rate Calculation Types:
- ABS (Absolute): Use specDayRate directly, ignore baseRate
- ADD (Additive): finalRate = baseRate + specDayRate
- PCT (Percentage Add): finalRate = baseRate + (baseRate × specDayRate / 100)
- PCO (Percentage Of): finalRate = baseRate × (specDayRate / 100)
Example Calculations:
Given: baseRate = 500, specDayRate = 100
ABS: finalRate = 100 (absolute override)
ADD: finalRate = 500 + 100 = 600
PCT: finalRate = 500 + (500 × 100/100) = 1000
PCO: finalRate = 500 × (100/100) = 500
Extra Occupant Fields (Normal Day)¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| adultRate | Double | Yes | Extra adult supplement | ADULT_RATE |
| adultRateType | String(3) | Yes | Adult rate calc type (ABS/ADD/PCT/PCO) | ADULT_RATE_TYPE |
| childRate | Double | Yes | Extra child supplement | CHILD_RATE |
| childRateType | String(3) | Yes | Child rate calc type | CHILD_RATE_TYPE |
Logic: Applied when occupancy exceeds rateBase (e.g., 3 adults in a DBL room)
Extra Occupant Fields (Special Day)¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| adultSdRate | Double | Yes | Extra adult supplement (special day) | ADULT_SD_RATE |
| childSdRate | Double | Yes | Extra child supplement (special day) | CHILD_SD_RATE |
Logic: Same as normal day supplements, but applied on special days
Extra Bed Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| extraBedRate | Double | Yes | Extra bed charge per night | EXTRA_BED_RATE |
Meal Plan Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| mealBase | Integer | Yes (FK) | Base meal plan ID included in rate | MEAL_BASE |
| mealSupplements | String(JSON) | Yes | Additional meal plan options | MEAL_SUPPLEMENTS |
mealSupplements JSON Structure:
[
{
"mealPlan": "BB",
"adlRate": [60.0, 70.0], // [normal, special day]
"chldRate": [30.0, 35.0]
},
{
"mealPlan": "HB",
"adlRate": [100.0, 120.0],
"chldRate": [50.0, 60.0]
},
{
"mealPlan": "FB",
"adlRate": [150.0, 180.0],
"chldRate": [75.0, 90.0]
}
]
Usage: If customer wants HB instead of base RO:
- Add adlRate[0] per adult (normal day) or adlRate[1] (special day)
- Add chldRate[0] per child (normal day) or chldRate[1] (special day)
Availability Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| available | Boolean | No | Room available for booking | AVAILABLE |
| stopSale | Boolean | No | Room on stop sale | STOP_SALE |
| onRequest | Boolean | No | Room available on request only | ON_REQUEST |
Availability Logic:
If stopSale = true:
→ Room cannot be booked, shown as "Not Available"
If available = false:
→ Room not available, hidden from search
If onRequest = true:
→ Room shown but marked "On Request" (requires confirmation)
If available = true AND stopSale = false:
→ Room bookable immediately
Minimum Length of Stay (MLOS) Fields¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| mlos | Integer | Yes | Minimum nights (normal day) | MLOS |
| sdmlos | Integer | Yes | Minimum nights (special day) | SDMLOS |
| mlosDesc | String(100) | Yes | MLOS description | MLOS_DESC |
Example:
Notes Field¶
| Attribute | Type | Nullable | Description | Database Column |
|---|---|---|---|---|
| notes | String(500) | Yes | Special notes for this date | NOTES |
Transient Fields (Not Stored in Database)¶
| Attribute | Type | Purpose |
|---|---|---|
| breakdown | String[] | Cost breakdown for display (transient) |
| calculatedCost | Double | Computed total cost (transient) |
Usage: Populated during search operations for UI display
Entity Methods¶
1. build(Map ent) - CHotelRoom.java:294¶
Creates new entity from JSON map:
public CRoomCalendarEntry build(Map ent) {
DateUtil du = DateUtil.getInstance();
// Extract all fields from map
this.roomId = TypeUtil.extractInteger(ent.get("roomId"));
this.market = (String) ent.get("market");
this.stayDate = du.fromString(
(String) ent.get("stayDate"),
DateUtil.WEB_DATE_FORMAT // dd-MM-yyyy
);
this.baseRate = TypeUtil.extractDouble(ent.get("baseRate"));
// ... (31 total fields)
return this;
}
2. update(Map ent) - CHotelRoom.java:340¶
Updates existing entity from partial JSON map:
public CRoomCalendarEntry update(Map ent) {
// Only update fields present in map
if(ent.containsKey("baseRate"))
this.baseRate = TypeUtil.extractDouble(ent.get("baseRate"));
if(ent.containsKey("specialDay"))
this.specialDay = TypeUtil.extractBool(ent.get("specialDay"));
// ... (for all updatable fields)
// IMPORTANT: stayDate is NOT updatable (line 379)
this.stayDate = null; // We do not update the date!!
return this;
}
Key Feature: The stayDate field is intentionally NOT updatable. To change the date, you must delete and recreate the entry.
3. copy(CRoomCalendarEntry ent) - CHotelRoom.java:429¶
Copies all fields from another entry:
public CRoomCalendarEntry copy(CRoomCalendarEntry ent) {
this.roomId = ent.roomId;
this.market = ent.market;
this.stayDate = ent.stayDate;
this.baseRate = ent.baseRate;
// ... (copies all 31 fields)
return this;
}
Usage: Used by "Copy Rates" feature to duplicate calendar entries across date ranges or markets.
Complex Pricing Calculation Algorithm¶
This entity supports sophisticated pricing calculations performed at the API layer:
For a given booking (checkIn, checkOut, adults, children, mealPlan):
1. Query all calendar entries for (roomId, market) WHERE stayDate BETWEEN checkIn AND (checkOut - 1)
2. If any night missing → Room not available for this period
3. For each night:
a. Determine if special day: specialDay = true
b. Get effective base rate:
- If specialDay AND specDayRate exists:
Apply sdRateType calculation
- Else:
Use baseRate
c. Determine occupancy from rateBase (SGL=1, DBL=2, TRPL=3, QPL=4)
d. Calculate extra adults:
extraAdults = max(0, adults - rateBase_occupancy)
e. Calculate extra adult charges:
- Use adultSdRate if special day, else adultRate
- Apply adultRateType calculation for each extra adult
f. Calculate extra child charges:
- Use childSdRate if special day, else childRate
- Apply childRateType calculation for each child
g. Add extra bed charge if required:
- If extraBedRequired AND adults > maxAdults:
nightCost += extraBedRate
h. Add meal supplement if different from mealBase:
- Find meal in mealSupplements JSON array
- Get adlRate[0 or 1] based on special day
- Get chldRate[0 or 1] based on special day
- nightCost += (adlRate × adults) + (chldRate × children)
i. Sum: nightCost = baseRate + adultCharges + childCharges + extraBed + mealSupplement
4. Total cost = sum of all night costs
5. Sell price = totalCost × (1 + margin%)
6. Return with breakdown for display
Business Rules¶
- Unique Constraint: (roomId, market, stayDate) must be unique
- Date Logic:
stayDateis the check-in date; check-out is not stored (implied by next day's entry) - Booking Window: Current date must be between
bookFromandbookTo - Rate Base: Must match one of {SGL, DBL, TRPL, QPL}
- Special Day Logic: If
specialDay = true, use special day rates; otherwise use normal rates - MLOS Validation: If check-in is on special day, stay length must be ≥
sdmlos; otherwise ≥mlos - Stop Sale Priority:
stopSale = trueoverrides all other availability settings - Meal Supplements: JSON array must be valid; invalid JSON treated as empty array
4. CMealPlan - Meal Plan Entity¶
File: tqapp/src/main/java/com/perun/tlinq/entity/hotel/CMealPlan.java
Purpose: Defines standard meal plan options available system-wide.
Attributes¶
| Attribute | Type | Nullable | Description | Example | Database Column |
|---|---|---|---|---|---|
| planId | Integer | No (PK) | Primary key | 3 | PLAN_ID |
| plan | String(10) | No | Meal plan code | "BB" | PLAN |
| name | String(50) | No | Display name | "Bed & Breakfast" | NAME |
| planDesc | String(200) | Yes | Description | "Includes daily breakfast" | PLAN_DESC |
| planOrder | Integer | Yes | Sort order for display | 2 | PLAN_ORDER |
| adultCost | Double | Yes | Default adult supplement | 50.00 | ADULT_COST |
| childCost | Double | Yes | Default child supplement | 25.00 | CHILD_COST |
Standard Meal Plans¶
| Plan Code | Name | Typical planOrder | Description |
|---|---|---|---|
| RO | Room Only | 1 | No meals included |
| BB | Bed & Breakfast | 2 | Daily breakfast |
| HB | Half Board | 3 | Breakfast + Dinner |
| FB | Full Board | 4 | Breakfast + Lunch + Dinner |
| AIL | All Inclusive Lite | 5 | All meals + local drinks |
| AI | All Inclusive | 6 | All meals + premium drinks |
| AIP | All Inclusive Plus | 7 | AI + premium services |
Business Rules¶
- Hierarchical Nature: Meal plans form a hierarchy from least (RO) to most (AIP) inclusive
- Cost Progression:
adultCostandchildCostshould increase withplanOrder - Child Discount:
childCosttypically 50% ofadultCost - Default Costs: The costs in this table are defaults; actual costs are stored in
CRoomCalendarEntry.mealSupplements - Plan Order: Used for UI sorting and display order
No build() Method¶
Unlike other entities, CMealPlan has no build() method. It's a relatively static reference table, typically pre-populated and rarely modified.
Database Schema (Inferred)¶
Table: HOTEL¶
CREATE TABLE HOTEL (
HOTEL_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
SH_DES VARCHAR(10) NOT NULL UNIQUE,
NAME VARCHAR(200) NOT NULL,
SHORT_NAME VARCHAR(50),
HOTEL_DESC TEXT,
PRODUCT_ID INTEGER,
DIRECT BOOLEAN DEFAULT FALSE,
INDIRECT BOOLEAN DEFAULT FALSE,
COUNTRY VARCHAR(3),
STARS INTEGER,
INFANT INTEGER DEFAULT 2,
CHILD INTEGER DEFAULT 12,
CHK_IN VARCHAR(5) DEFAULT '14:00',
CHK_OUT VARCHAR(5) DEFAULT '12:00',
FEES DECIMAL(10,2),
FEES_DESC VARCHAR(500),
TERMS TEXT,
AREA VARCHAR(3),
CITY VARCHAR(50),
RELEASE INTEGER,
RESERVATION_CONTACT VARCHAR(200),
CONSTRAINT CHK_STARS CHECK (STARS BETWEEN 1 AND 5),
CONSTRAINT CHK_CONTRACT CHECK (DIRECT = TRUE OR INDIRECT = TRUE),
CONSTRAINT CHK_AGE CHECK (INFANT < CHILD)
);
CREATE INDEX IDX_HOTEL_AREA ON HOTEL(AREA);
CREATE INDEX IDX_HOTEL_COUNTRY ON HOTEL(COUNTRY);
CREATE INDEX IDX_HOTEL_NAME ON HOTEL(NAME);
Table: HOTEL_ROOM¶
CREATE TABLE HOTEL_ROOM (
ROOM_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
HOTEL_ID INTEGER NOT NULL,
NAME VARCHAR(100) NOT NULL,
ROOM_DESC TEXT,
MAX_OCCUPANCY INTEGER NOT NULL,
MAX_ADULTS INTEGER NOT NULL,
EXTRA_BED BOOLEAN NOT NULL DEFAULT FALSE,
EXTRA_BED_REQUIRED BOOLEAN NOT NULL DEFAULT FALSE,
BEDDING VARCHAR(100),
NOTES VARCHAR(500),
FOREIGN KEY (HOTEL_ID) REFERENCES HOTEL(HOTEL_ID) ON DELETE CASCADE,
CONSTRAINT CHK_OCCUPANCY CHECK (MAX_ADULTS <= MAX_OCCUPANCY),
CONSTRAINT CHK_EXTRA_BED CHECK (
EXTRA_BED_REQUIRED = FALSE OR EXTRA_BED = TRUE
)
);
CREATE INDEX IDX_ROOM_HOTEL ON HOTEL_ROOM(HOTEL_ID);
CREATE INDEX IDX_ROOM_NAME ON HOTEL_ROOM(HOTEL_ID, NAME);
Table: MEAL_PLAN¶
CREATE TABLE MEAL_PLAN (
PLAN_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
PLAN VARCHAR(10) NOT NULL UNIQUE,
NAME VARCHAR(50) NOT NULL,
PLAN_DESC VARCHAR(200),
PLAN_ORDER INTEGER,
ADULT_COST DECIMAL(10,2),
CHILD_COST DECIMAL(10,2)
);
CREATE INDEX IDX_MEAL_PLAN_ORDER ON MEAL_PLAN(PLAN_ORDER);
-- Seed data
INSERT INTO MEAL_PLAN (PLAN, NAME, PLAN_ORDER, ADULT_COST, CHILD_COST) VALUES
('RO', 'Room Only', 1, 0.00, 0.00),
('BB', 'Bed & Breakfast', 2, 50.00, 25.00),
('HB', 'Half Board', 3, 100.00, 50.00),
('FB', 'Full Board', 4, 150.00, 75.00),
('AIL', 'All Inclusive Lite', 5, 200.00, 100.00),
('AI', 'All Inclusive', 6, 250.00, 125.00),
('AIP', 'All Inclusive Plus', 7, 300.00, 150.00);
Table: ROOM_CALENDAR_ENTRY¶
CREATE TABLE ROOM_CALENDAR_ENTRY (
CALENDAR_ENTRY_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
ROOM_ID INTEGER NOT NULL,
MARKET VARCHAR(3) NOT NULL,
STAY_DATE DATE NOT NULL,
VENDOR_ID INTEGER,
PROMO VARCHAR(20),
BOOK_FROM DATE,
BOOK_TO DATE,
BASE_RATE DECIMAL(10,2) NOT NULL,
SPEC_DAY_RATE DECIMAL(10,2),
SPECIAL_DAY BOOLEAN NOT NULL DEFAULT FALSE,
SD_RATE_TYPE VARCHAR(3),
ADULT_RATE DECIMAL(10,2),
ADULT_RATE_TYPE VARCHAR(3),
CHILD_RATE DECIMAL(10,2),
CHILD_RATE_TYPE VARCHAR(3),
ADULT_SD_RATE DECIMAL(10,2),
CHILD_SD_RATE DECIMAL(10,2),
EXTRA_BED_RATE DECIMAL(10,2),
RATE_BASE VARCHAR(4) NOT NULL,
MEAL_BASE INTEGER,
MEAL_SUPPLEMENTS TEXT, -- JSON array
STOP_SALE BOOLEAN NOT NULL DEFAULT FALSE,
AVAILABLE BOOLEAN NOT NULL DEFAULT TRUE,
ON_REQUEST BOOLEAN NOT NULL DEFAULT FALSE,
MLOS INTEGER,
SDMLOS INTEGER,
MLOS_DESC VARCHAR(100),
NOTES VARCHAR(500),
FOREIGN KEY (ROOM_ID) REFERENCES HOTEL_ROOM(ROOM_ID) ON DELETE CASCADE,
FOREIGN KEY (MEAL_BASE) REFERENCES MEAL_PLAN(PLAN_ID),
UNIQUE KEY UQ_ROOM_MARKET_DATE (ROOM_ID, MARKET, STAY_DATE),
CONSTRAINT CHK_MARKET CHECK (MARKET IN ('UAE', 'GCC', 'ROW')),
CONSTRAINT CHK_RATE_BASE CHECK (RATE_BASE IN ('SGL', 'DBL', 'TRPL', 'QPL')),
CONSTRAINT CHK_RATE_TYPE CHECK (
SD_RATE_TYPE IS NULL OR SD_RATE_TYPE IN ('ABS', 'ADD', 'PCT', 'PCO')
),
CONSTRAINT CHK_BOOKING_WINDOW CHECK (BOOK_FROM IS NULL OR BOOK_TO IS NULL OR BOOK_FROM <= BOOK_TO)
);
CREATE INDEX IDX_CAL_ROOM ON ROOM_CALENDAR_ENTRY(ROOM_ID);
CREATE INDEX IDX_CAL_DATE ON ROOM_CALENDAR_ENTRY(STAY_DATE);
CREATE INDEX IDX_CAL_MARKET ON ROOM_CALENDAR_ENTRY(MARKET);
CREATE INDEX IDX_CAL_SEARCH ON ROOM_CALENDAR_ENTRY(ROOM_ID, MARKET, STAY_DATE);
CREATE INDEX IDX_CAL_AVAILABLE ON ROOM_CALENDAR_ENTRY(AVAILABLE, STOP_SALE);
Database Indexes Strategy¶
Primary Indexes (Auto-created)¶
- Primary keys: HOTEL_ID, ROOM_ID, PLAN_ID, CALENDAR_ENTRY_ID
- Unique constraints: HOTEL.SH_DES, MEAL_PLAN.PLAN, ROOM_CALENDAR_ENTRY(ROOM_ID, MARKET, STAY_DATE)
Performance Indexes¶
Hotel Searches:
- IDX_HOTEL_AREA: Filter by area/emirate
- IDX_HOTEL_NAME: Search by name (prefix matching)
- IDX_HOTEL_COUNTRY: Filter by country
Room Lookups:
- IDX_ROOM_HOTEL: Get all rooms for a hotel
- IDX_ROOM_NAME: Search room by hotel and name
Calendar Searches (Critical for Performance):
- IDX_CAL_SEARCH: Composite index (roomId, market, stayDate) - supports unique constraint and fast lookups
- IDX_CAL_DATE: Range queries on stay dates
- IDX_CAL_AVAILABLE: Filter by availability status
Meal Plans:
- IDX_MEAL_PLAN_ORDER: Ordered display of meal plans
Data Integrity and Constraints¶
Foreign Key Relationships¶
- HOTEL_ROOM.HOTEL_ID → HOTEL.HOTEL_ID
- CASCADE on delete: If hotel deleted, all rooms deleted
-
RESTRICT on update: Hotel ID cannot be changed
-
ROOM_CALENDAR_ENTRY.ROOM_ID → HOTEL_ROOM.ROOM_ID
- CASCADE on delete: If room deleted, all calendar entries deleted
-
RESTRICT on update: Room ID cannot be changed
-
ROOM_CALENDAR_ENTRY.MEAL_BASE → MEAL_PLAN.PLAN_ID
- SET NULL on delete: If meal plan deleted, calendar entry retains pricing but meal plan reference is nulled
- RESTRICT on update: Meal plan ID cannot be changed
Check Constraints¶
- Hotel Constraints:
- Star rating between 1-5
- At least one contract type (direct or indirect) must be true
-
Infant age < Child age
-
Room Constraints:
- Max adults ≤ Max occupancy
-
If extra bed required, extra bed must be available
-
Calendar Constraints:
- Market must be UAE, GCC, or ROW
- Rate base must be SGL, DBL, TRPL, or QPL
- Rate calculation types must be ABS, ADD, PCT, or PCO
- Booking window: book_from ≤ book_to
Unique Constraints¶
- HOTEL.SH_DES: Hotel code must be unique across all hotels
- MEAL_PLAN.PLAN: Meal plan code must be unique
- (ROOM_ID, MARKET, STAY_DATE): Only one rate per room/market/date combination
Data Volume Estimates¶
Typical Deployment¶
| Table | Estimated Rows | Growth Rate | Notes |
|---|---|---|---|
| HOTEL | 100-500 | Slow (10-20/year) | Contract hotels only |
| HOTEL_ROOM | 500-5,000 | Slow | Avg 10 room types per hotel |
| MEAL_PLAN | 7-15 | Very slow | Reference data |
| ROOM_CALENDAR_ENTRY | 500,000-5,000,000 | Fast | Grows daily |
Calendar Entry Growth¶
Per room per market:
365 days/year × 2 years advance = 730 entries
1000 rooms × 3 markets × 730 days = 2,190,000 entries
Growth Pattern: - Continuous: New dates added as calendar extends forward - Bulk operations: Common to create 90-365 days at once - Updates: Frequent rate changes, especially near booking dates - Purge strategy: Archive entries older than 1 year
JSON Data Structures¶
mealSupplements Field¶
Storage: TEXT column in ROOM_CALENDAR_ENTRY table Format: JSON array of meal plan supplements
Structure:
[
{
"mealPlan": "BB",
"adlRate": [60.0, 70.0],
"chldRate": [30.0, 35.0]
},
{
"mealPlan": "HB",
"adlRate": [100.0, 120.0],
"chldRate": [50.0, 60.0]
}
]
Field Descriptions:
- mealPlan (String): Meal plan code (matches MEAL_PLAN.PLAN)
- adlRate (Array[2]): Adult supplement [normal_day, special_day]
- chldRate (Array[2]): Child supplement [normal_day, special_day]
Parsing Logic (JavaScript - hotelmgmt.js):
let mealSupplements = [];
if (calEntry.mealSupplements) {
try {
mealSupplements = JSON.parse(calEntry.mealSupplements);
} catch(e) {
console.error("Invalid meal supplements JSON", e);
mealSupplements = [];
}
}
// Find supplement for requested meal plan
let supplement = mealSupplements.find(s => s.mealPlan === requestedMealPlan);
if (supplement) {
let isSpecialDay = calEntry.specialDay;
let adultSupplement = isSpecialDay ? supplement.adlRate[1] : supplement.adlRate[0];
let childSupplement = isSpecialDay ? supplement.chldRate[1] : supplement.chldRate[0];
totalMealCost = (adults × adultSupplement) + (children × childSupplement);
}
Entity Lifecycle¶
Hotel Lifecycle¶
1. CREATE
- User fills hotel form
- POST /hotel/saveHotel with hotelId = null
- Database assigns new hotelId
- Hotel added to search results
2. READ
- POST /hotel/listHotels (search)
- POST /hotel/hotelLookup (autocomplete)
- POST /hotel/getHotel (details)
3. UPDATE
- User edits hotel form
- POST /hotel/saveHotel with existing hotelId
- Database updates record
4. DELETE
- No explicit delete endpoint
- Manual database operation (cascades to rooms and calendar)
Room Lifecycle¶
1. CREATE
- User selects hotel, clicks "Add Room"
- Fills room form
- POST /hotel/saveRoom with roomId = null
- Database assigns new roomId
- Room added to hotel's room list
2. READ
- POST /hotel/listHotelRooms with hotelId
- Returns all rooms for hotel
3. UPDATE
- User edits room form
- POST /hotel/saveRoom with existing roomId
- Database updates record
4. DELETE
- No explicit delete endpoint
- Manual database operation (cascades to calendar)
Calendar Entry Lifecycle¶
1. CREATE (Bulk)
- User selects room, market, clicks "Set Pricing"
- Fills calendar creation form with date range
- POST /hotel/createRoomCalendar
- System creates one entry per day in range
- Special days automatically flagged (weekends)
- Returns count of entries created
2. READ
- POST /hotel/listRoomCalendar with roomId, market, date range
- Returns array of calendar entries for editing
3. UPDATE (Bulk)
- User edits date range in calendar view
- POST /hotel/updateRoomCalendar with entry updates
- System updates all entries in selection
- Can update rates, availability, MLOS, etc.
4. COPY
- User selects source range, clicks "Copy Rates"
- POST /hotel/copyRoomCalendar with source and target params
- System copies entries to new dates/markets
- Preserves all pricing logic
5. DELETE
- No explicit delete endpoint
- Manual database operation
Meal Plan Lifecycle¶
Meal plans are relatively static reference data:
1. CREATE
- Typically pre-populated during system setup
- Or manually added via SQL
2. READ
- POST /hotel/listMealPlans
- Returns all available meal plans
3. UPDATE
- POST /hotel/updateMealPlans (bulk update)
- Updates costs for multiple plans
4. DELETE
- Not supported (reference data)
Data Validation Rules¶
Field-Level Validation¶
CHotel¶
shDes: Required, 1-10 chars, alphanumeric, uniquename: Required, 1-200 charsstars: Optional, integer 1-5infant: Optional, integer 0-17, default 2child: Optional, integer 0-17, default 12, must be > infantchkIn,chkOut: Optional, format "HH:MM" (24-hour)fees: Optional, non-negative decimalrelease: Optional, non-negative integer (days)
CHotelRoom¶
hotelId: Required, must exist in HOTEL tablename: Required, 1-100 charsmaxOccupancy: Required, integer ≥ 1maxAdults: Required, integer ≥ 1, ≤ maxOccupancyextraBed: Required, booleanextraBedRequired: Required, boolean; if true, extraBed must be true
CRoomCalendarEntry¶
roomId: Required, must exist in HOTEL_ROOM tablemarket: Required, must be "UAE", "GCC", or "ROW"stayDate: Required, date format dd-MM-yyyy (web) or yyyy-MM-dd (db)baseRate: Required, positive decimalrateBase: Required, must be "SGL", "DBL", "TRPL", or "QPL"sdRateType: Optional, must be "ABS", "ADD", "PCT", or "PCO" if providedbookFrom,bookTo: Optional, bookFrom ≤ bookTomlos,sdmlos: Optional, integer ≥ 1mealSupplements: Optional, valid JSON array if provided
CMealPlan¶
plan: Required, 1-10 chars, uniquename: Required, 1-50 charsadultCost,childCost: Optional, non-negative decimal
Business Logic Validation¶
- Date Range Validation:
- Stay period: from < to
-
Booking window: bookFrom < bookTo < stayDate
-
Occupancy Validation:
- Booking adults ≤ room.maxAdults (or + extra bed)
-
Booking (adults + children) ≤ room.maxOccupancy
-
Rate Calculation Validation:
- If sdRateType provided, specDayRate must be provided
-
If adultRateType provided, adultRate must be provided
-
MLOS Validation:
- Booking length ≥ mlos (normal days)
- Booking length ≥ sdmlos (if special day included)
Summary¶
The Hotel Management data model consists of 4 entities forming a hierarchical structure:
- CHotel: Hotel master data (22 attributes)
- CHotelRoom: Room types per hotel (10 attributes)
- CRoomCalendarEntry: Daily pricing per room/market (31 attributes)
- CMealPlan: Meal plan reference data (7 attributes)
Key Features: - Flexible pricing: Supports multiple markets, special days, occupancy-based rates - Rate calculation types: ABS, ADD, PCT, PCO for dynamic pricing - Meal plan supplements: JSON-based flexible meal pricing - MLOS restrictions: Different minimums for normal and special days - Availability control: Stop sale, on-request, booking windows - Bulk operations: Calendar creation, updates, and copying
Database Design: - Normalized structure: Clear foreign key relationships - Performance indexes: Optimized for search and calendar queries - Data integrity: Check constraints and unique constraints - JSON flexibility: mealSupplements field allows structured data without additional tables
This data model supports complex hotel contracting scenarios common in travel agencies while maintaining performance for real-time availability searches.