Cruise Offer Management - Backend Implementation¶
This document details the backend implementation of the Cruise Offer Management module for TQPro (TourLinq Professional).
Table of Contents¶
- Architecture Overview
- Database Schema
- Entity Classes
- API Layer
- Business Logic (Facade)
- Front-End Super-APIs
- Security Implementation
- Error Handling
- API Reference
- Super-API Endpoints
Architecture Overview¶
The Cruise Offer Management module follows the standard TQPro three-layer architecture:
┌─────────────────────────────────────────────────────────────┐
│ REST API Layer (tqapi) │
│ CruiseApi.java │
│ Jersey JAX-RS endpoints, request/response handling │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Business Logic Layer (tqapp) │
│ CruiseFacade.java │
│ CRUD operations, business rules, status workflows │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Entity Framework (tqcommon) │
│ EntityFacade, EntityTransformer, NTSServiceFactory │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Database (nts schema) │
│ Native entities via Hibernate ORM │
└─────────────────────────────────────────────────────────────┘
Key Components¶
| Component | Location | Purpose |
|---|---|---|
CruiseApi.java |
tqapi/src/main/java/com/perun/tlinq/api/ |
REST endpoints |
CruiseFacade.java |
tqapp/src/main/java/com/perun/tlinq/entity/cruise/ |
Business logic |
CruiseValidationUtil.java |
tqapp/src/main/java/com/perun/tlinq/entity/cruise/ |
Input validation |
CruisePricingUtil.java |
tqapp/src/main/java/com/perun/tlinq/entity/cruise/ |
Retail pricing calculations |
CruiseException.java |
tqapp/src/main/java/com/perun/tlinq/entity/cruise/ |
Error handling |
cruise-entities.xml |
config/entities/ |
Entity configuration |
cruises.sql |
config/db-changes/ |
Database schema |
Database Schema¶
The cruise module uses 12 database tables in the nts schema:
Entity Relationship Diagram¶
┌──────────────────┐
│ cruisecompany │
│ ────────────── │
│ companyid (PK) │
│ code (UQ) │
│ name │
└────────┬─────────┘
│ 1:N
▼
┌──────────────────┐ ┌──────────────────┐
│ cruiseship │ │ cruisearea │
│ ──────────── │ │ ────────── │
│ shipid (PK) │ │ areaid (PK) │
│ code (UQ) │ │ code (UQ) │
│ companyid (FK) │ │ description │
│ name │ └────────┬─────────┘
│ description │ │
│ docurl │ │
└─────┬───────┬────┘ │
│ │ │
│ │ ┌───────────────┘
│ │ │
│ 1:N │ │ 1:N
│ │ │
▼ │ ▼
┌──────────┐ │ ┌──────────────────┐
│ shipcabin│ │ │ itinerary │
│ ─────────│ │ │ ────────── │
│shipcabinid│ │ │ itineraryid (PK)│
│ shipid(FK)│ └──│ shipid (FK) │
│cabintypeid│ │ areaid (FK) │
│ maxpax │ │ code (UQ) │
│description│ │ name │
└─────┬─────┘ │ duration │
│ │ imageurl │
│ │ status │
│ └─────┬───────┬────┘
│ │ │
│ │ 1:N │ 1:N
│ │ │
│ ▼ ▼
│ ┌───────────┐ ┌──────────────────┐
│ │cruisetempl│ │ cruise │
│ │ ──────────│ │ ────── │
│ │cruisetmplid│ │ cruiseid (PK) │
│ │itineraryid│ │ itineraryid(FK) │
│ │cruiseportid│ │ startdate │
│ │ stopseq │ │ status │
│ │dayoffsetarr│ └───────┬─────────┘
│ │dayoffsetdep│ │
│ │arrivetime │ │
│ │departtime │ │
│ │description│ │
│ └───────────┘ │
│ │
│ ┌───────────────┼───────────────┐
│ │ │ │
│ ▼ ▼ ▼
│ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ │cruisepoint│ │cabincharge│ │othercharge│
│ │ ──────────│ │ ──────────│ │ ──────────│
│ │cruisepntid│ │ chargeid │ │ chargeid │
│ │ cruiseid │ │ cruiseid │ │ cruiseid │
│ │cruiseportid│ │shipcabinid│ │chargetypeid│
│ │ stopseq │ │ amount │ │ amount │
│ │ arrivedt │ │ currency │ │ currency │
│ │ departdt │ │ exrate │ │ exrate │
│ │description│ │ available │ │ mandatory │
│ └───────────┘ └─────┬─────┘ │ paxtype │
│ │ └─────┬─────┘
│ │ │
└───────────────────────────────┘ │
│
┌──────────────────┐ ┌──────────────────┐
│ cabintype │ │ chargetype │
│ ────────── │ │ ────────── │
│ cabinid (PK) │ │ chargetypeid(PK) │
│ code (UQ) │ │ code (UQ) │
│ name │ │ name │
└──────────────────┘ └──────────────────┘
┌──────────────────┐
│ cruiseport │
│ ────────── │
│ cruiseportid(PK) │
│ code (UQ) │
│ name │
└──────────────────┘
Booking Tables (Phase 4)¶
┌──────────────────┐
│ cabinprebooking │
│ ────────────── │
│ prebookingid(PK) │
│ cruiseid (FK) │───► cruise
│ shipcabinid(FK) │───► shipcabin
│ costpercabin │
│ pricepercabin │
│ commission │
│ status │
│ createddate │
└──────────────────┘
┌──────────────────┐
│ cruisebooking │
│ ────────────── │
│ bookingid (PK) │
│ bookingnumber │
│ cruiseid (FK) │───► cruise
│ leadpassenger │
│ totalpax │
│ totaladults │
│ totalchildren │
│ totalinfants │
│ totalamount │
│ status │
│ voucherurl │
│ createddate │
└────────┬─────────┘
│ 1:N
┌────┼────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────────┐
│cruisepassgr│ │bookedcabn│ │ bookingaddon │
│ ──────────│ │ ─────────│ │ ────────────│
│passengerid │ │bookedcbnid│ │ addonid │
│ bookingid │ │ bookingid │ │ bookingid │
│ firstname │ │shipcabinid│ │ passengerid │
│ lastname │ │ numpax │ │chargetypeid │
│ nationality│ │ isown │ │ amount │
│dateofbirth │ │ price │ │ currency │
│passportnum │ │ status │ └──────────────┘
│passportexp │ │prebookngid│
│isleadpax │ └──────────┘
│ remarks │
└────────────┘
Tables Summary¶
| Table | Description | Key Fields |
|---|---|---|
cruisecompany |
Cruise line companies | code, name |
cruiseship |
Ships belonging to companies | code, name, companyid |
cruisearea |
Geographic cruise regions | code, description |
cruiseport |
Ports of call | code, name |
cabintype |
Cabin category definitions | code, name |
chargetype |
Additional fee types | code, name |
shipcabin |
Ship-cabin type assignments | shipid, cabintypeid, maxpax |
itinerary |
Route templates | code, shipid, areaid, duration, status |
cruisetemplate |
Itinerary stop templates | itineraryid, cruiseportid, stopseq |
cruise |
Scheduled cruise instances | itineraryid, startdate, status |
cruisepoint |
Actual cruise stops | cruiseid, cruiseportid, stopseq |
cabincharge |
Cabin prices per cruise | cruiseid, shipcabinid, amount |
othercharge |
Additional fees per cruise | cruiseid, chargetypeid, amount, paxtype |
cabinpricetemplate |
Default cabin pricing per itinerary | itineraryid, shipcabinid, amount |
otherchargetemplate |
Default other charges per itinerary | itineraryid, chargetypeid, amount, indicator |
cabinprebooking |
Pre-booked cabin inventory | cruiseid, shipcabinid, status, costpercabin |
cruisebooking |
Master booking records | cruiseid, bookingnumber, status, totalamount |
cruisepassenger |
Passenger details per booking | bookingid, firstname, lastname, nationality |
bookedcabin |
Cabin assignments per booking | bookingid, shipcabinid, isown, prebookingid |
bookingaddon |
Optional add-on charges | bookingid, passengerid, chargetypeid, amount |
Unique Constraints¶
-- Code uniqueness
uq_cruisecompanycode (cruisecompany.code)
uq_cruiseshipcode (cruiseship.code)
uq_cruseareacode (cruisearea.code)
uq_cruiseportcode (cruiseport.code)
uq_cabintypecode (cabintype.code)
uq_chargetypecode (chargetype.code)
uq_itinerarycode (itinerary.code)
-- Composite uniqueness
uq_shipcabin_ship_type (shipcabin.shipid, shipcabin.cabintypeid)
uq_cabincharge_cruise_cabin (cabincharge.cruiseid, cabincharge.shipcabinid)
uq_othercharge_cruise_type (othercharge.cruiseid, othercharge.chargetypeid)
Entity Classes¶
Canonical Entities (tqapp)¶
Located in tqapp/src/main/java/com/perun/tlinq/entity/cruise/:
| Class | Description | Key Properties |
|---|---|---|
CCruiseCompany |
Cruise company | companyId, companyCode, companyName, color |
CCruiseShip |
Ship | cruiseShipId, shipCode, shipName, companyId, docUrl |
CCruiseArea |
Geographic area | cruiseAreaId, areaCode, areaName |
CCruisePort |
Port of call | cruisePortId, portCode, portName |
CCabinType |
Cabin category | cabinTypeId, typeCode, typeName |
CChargeType |
Fee type | chargeTypeId, chargeTypeCode, chargeTypeName |
CShipCabin |
Ship-cabin assignment | shipCabinId, shipId, cabinTypeId, maxPax |
CCruiseItinerary |
Route template | itineraryId, cruiseShipId, cruiseAreaId, companyId*, duration, status |
CCruiseTemplate |
Itinerary stop | cruiseTemplateId, itineraryId, portId, stopSeq |
CCruise |
Scheduled cruise | cruiseId, itineraryId, startDate, status |
CCruisePoint |
Cruise stop | cruisePointId, cruiseId, portId, stopSeq |
CCabinCharge |
Cabin price | cabinChargeId, cruiseId, shipCabinId, amount, currency |
COtherCharge |
Additional fee | otherChargeId, cruiseId, chargeTypeId, amount, paxType, indicator |
COtherChargeTemplate |
Other charge template | otherChargeTemplateId, itineraryId, chargeTypeId, indicator |
CCabinPriceTemplate |
Cabin price template | cabinPriceTemplateId, itineraryId, shipCabinId |
CCabinPreBooking |
Pre-booking record | preBookingId, cruiseId, shipCabinId, status, costPerCabin, pricePerCabin, commission |
CCruiseBooking |
Booking record | bookingId, bookingNumber, cruiseId, leadPassengerName, totalPax, totalAmount, status |
CCruisePassenger |
Passenger record | passengerId, bookingId, firstName, lastName, nationality, dateOfBirth, isLeadPassenger |
CBookedCabin |
Booked cabin | bookedCabinId, bookingId, shipCabinId, numPax, isOwn, price, preBookingId |
CBookingAddon |
Booking add-on | addonId, bookingId, passengerId, chargeTypeId, amount, currency |
Composite/Response Entities¶
| Class | Purpose |
|---|---|
CCruiseSearch |
Search request/response wrapper |
CCruiseSearchResultItem |
Individual search result |
CCruiseCabinRecord |
Cabin with charge info |
CCruiseChargeRecord |
Charge with type info |
CCruisePointRecord |
Point with port info |
CCruiseAvailability |
Booking availability response |
CPriceCalculation |
Price calculation result |
CPriceLineItem |
Individual price line |
CItineraryTreeNode |
Hierarchical tree node |
CItineraryTreeItem |
Itinerary in tree |
CAllCruisesResponse |
Super-API: all available cruises grouped by itinerary |
CItineraryCruisesResponse |
Super-API: itinerary cruises with pricing |
CItineraryCruiseItem |
Single cruise with min price per pax |
CCruiseDetailResponse |
Super-API: cruise detail with cabin pricing |
CCruiseCabinPriced |
Cabin with calculated retail price |
Entity Configuration¶
Entity mappings are defined in config/entities/cruise-entities.xml:
<Entity name="Cruise" class="com.perun.tlinq.entity.cruise.CCruise"
idField="cruiseId" defaultFactory="NTSServiceFactory">
<EntityFactoryList>
<Factory name="NTSServiceFactory"
nativeEntity="com.perun.tlinq.client.nts.db.cruise.CruiseEntity">
<ServiceList>
<Service name="saveCruise" action="create"/>
<Service name="saveCruise" action="update"/>
<Service name="readCruise" action="read"/>
<Service name="readCruise" action="search"/>
<Service name="deleteCruise" action="delete"/>
</ServiceList>
<FieldMappingList>
<FieldMapping targetField="cruiseId" sourceField="id" mapping="DirectMapping"/>
<FieldMapping targetField="itineraryId" sourceField="itineraryid" mapping="DirectMapping"/>
<FieldMapping targetField="startDate" sourceField="startdate" mapping="DirectMapping"/>
<FieldMapping targetField="status" sourceField="status" mapping="DirectMapping"/>
</FieldMappingList>
</Factory>
</EntityFactoryList>
</Entity>
API Layer¶
CruiseApi.java¶
The REST API is implemented in tqapi/src/main/java/com/perun/tlinq/api/CruiseApi.java.
Base Path: /cruise
Endpoint Pattern¶
Each entity follows a consistent CRUD pattern:
@POST
@Path("/entity/list") // Search/list records
@Path("/entity/read") // Read single record by ID
@Path("/entity/write") // Create or update record
@Path("/entity/delete") // Delete record
Request/Response Format¶
All endpoints accept and return JSON:
Request:
Success Response:
Error Response:
{
"apiStatus": {
"errorCode": "CRU0001",
"errorMessage": "Itinerary not found: 123"
},
"apiData": null
}
Endpoint Categories¶
| Category | Endpoints | Operations |
|---|---|---|
| Company | /company/* |
list, read, write, delete |
| Area | /area/* |
list, read, write, delete |
| Ship | /ship/* |
list, read, write, delete |
| Cabin Type | /cabintype/* |
list, read, write, delete |
| Charge Type | /chargetype/* |
list, read, write, delete |
| Port | /port/* |
list, read, write, delete |
| Ship Cabin | /shipcabin/* |
list, read, write, delete, bulkAssign |
| Itinerary | /itinerary/* |
list, read, write, delete, changeStatus, tree, cruises |
| Template | /template/* |
list, read, write, delete |
| Cruise | /cruise/* |
list, read, write, delete, changeStatus |
| Cruise Point | /cruisepoint/* |
list, write, delete, regenerate |
| Cabin Charge | /cabincharge/* |
list, write, delete, initForCruise |
| Other Charge | /othercharge/* |
list, write, delete |
| Booking | /booking/* |
availability, calculatePrice |
| Pre-Booking | /prebooking/* |
list, write, delete, manage |
| Booking | /booking/* |
list, read, write, delete, changeStatus |
| Passenger | /passenger/* |
list, write, delete |
| Booked Cabin | /bookedcabin/* |
list, write, delete |
| Booking Add-on | /addon/* |
list, write, delete |
| Super-APIs | /itinerary/cruises, /detail |
Front-end retail pricing |
Business Logic (Facade)¶
CruiseFacade.java¶
Located in tqapp/src/main/java/com/perun/tlinq/entity/cruise/CruiseFacade.java.
Core Operations¶
CRUD Operations¶
Each entity type has standard CRUD methods:
// Create
public CCruiseCompany createNewCompany(String code, String name)
public CCruiseShip createShip(Integer companyId, String code, String name, ...)
public CCruiseItinerary createItinerary(Integer shipId, Integer areaId, ...)
// Read
public CCruiseCompany getCruiseCompany(Integer id)
public CCruiseShip getShip(Integer shipId)
public CCruiseItinerary getItinerary(Integer itinId)
// Update
public CCruiseCompany updateCompany(CCruiseCompany comp)
public CCruiseShip updateShip(CCruiseShip ship)
public CCruiseItinerary updateItinerary(CCruiseItinerary itinerary)
// Delete (with validation)
public void deleteCompany(Integer companyId)
public void deleteShip(Integer shipId)
public void deleteItinerary(Integer itineraryId)
// Search
public List<CCruiseCompany> getCruiseCompanies(String code, String name)
public List<CCruiseShip> searchShips(Integer companyId, String shipName)
public List<CCruiseItinerary> searchItinerary(Integer areaId, Integer shipId, ...)
Status Workflows¶
Itinerary Status¶
┌─────────┐ activate ┌─────────┐
│ Draft │ ────────────────► │ Active │
└─────────┘ (needs templates) └────┬────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ │ ▼
┌───────────┐ │ ┌──────────┐
│ Suspended │◄──────┘ │ Archived │
└─────┬─────┘ └──────────┘
│ ▲
│ reactivate │
└────────────────────────────┘
(if no future cruises)
Valid Transitions:
- Draft → Active (requires at least one cruise template)
- Active → Suspended
- Suspended → Active
- Active/Suspended → Archived (if no future cruises)
Cruise Status¶
┌─────────┐ activate ┌─────────┐
│ Draft │ ────────────────► │ Active │
└─────────┘ └────┬────┘
│
┌──────────────┼──────────────┐
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ Cancelled │ │ Completed │
└───────────┘ └───────────┘
(≥7 days before) (after end date)
Valid Transitions:
- Draft → Active
- Active → Cancelled (must be at least 7 days before departure)
- Active → Completed (only after cruise end date)
Special Operations¶
Cruise Skeleton Creation¶
Creates a cruise with all points, cabin charges, and other charges pre-populated:
This method: 1. Creates the cruise record 2. Generates cruise points from itinerary templates 3. Creates cabin charge records for all ship cabins 4. Creates other charge records for all charge types
Cruise Points Regeneration¶
Regenerates cruise points from itinerary templates:
Only allowed for cruises in Draft status.
Bulk Cabin Assignment¶
Assigns multiple cabin types to a ship:
public List<CShipCabin> bulkAssignCabinTypes(Integer shipId, List<Integer> cabinTypeIds, Integer defaultMaxPax)
Initialize Cabin Charges¶
Creates default cabin charges for all ship cabins for a cruise:
public List<CCabinCharge> initCabinChargesForCruise(Integer cruiseId, Double defaultAmount, String defaultCurrency)
Itinerary Tree¶
Returns hierarchical view of companies → ships → itineraries:
Booking Interface¶
Availability Check¶
Returns complete cruise details for booking:
Returns: - Cruise details - Itinerary information - Ship and company info - Available cabins with charges - Mandatory and optional charges - Cruise points with port details
Price Calculation¶
Calculates total price for a booking:
public CPriceCalculation calculatePrice(
Integer cruiseId,
Integer shipCabinId,
Integer adults,
Integer children,
List<Integer> selectedOptionalChargeIds)
Charge types by paxType:
- all - per passenger (adults + children)
- adult - per adult only
- child - per child only
- per_cabin - flat rate per cabin
Booking System¶
Booking Status Workflow¶
┌────────────────────┐
│ Documents pending │
└─────────┬──────────┘
│
┌─────┼──────────────────────┐
│ ▼ │
│ ┌────────────────────┐ │
│ │Availability pending│ │
│ └─────────┬──────────┘ │
│ │ │
│ ┌─────┘ │
│ ▼ ▼
│ ┌────────────────────┐ ┌──────────────────────┐
│ │ Payment pending │ │Cancelled-NonRefundable│
│ └─────────┬──────────┘ └──────────────────────┘
│ │
│ ┌─────┼──────────────────┐
│ │ ▼ ▼
│ │ ┌──────────────┐ ┌────────────────────────┐
│ │ │Voucher pending│ │Cancelled-RefundPending │
│ │ └──────┬───────┘ └────────────┬───────────┘
│ │ │ │
│ │ ▼ ▼
│ │ ┌──────────────┐ ┌────────────────────────┐
│ │ │ Vouchered │ │ Cancelled-Refunded │
│ │ └──────┬───────┘ └────────────────────────┘
│ │ │
│ │ └───────► Cancelled-RefundPending
│ │
└─────┘
Valid Transitions:
- Documents pending → Availability pending, Payment pending, Cancelled-NonRefundable
- Availability pending → Payment pending, Cancelled-NonRefundable
- Payment pending → Voucher pending, Cancelled-NonRefundable, Cancelled-RefundPending
- Voucher pending → Vouchered, Cancelled-RefundPending
- Vouchered → Cancelled-RefundPending
- Cancelled-RefundPending → Cancelled-Refunded
When the booking status changes to Vouchered, the system automatically transitions all associated pre-bookings from Reserved to Booked. This is done by iterating through the booking's booked cabins, loading each associated pre-booking, and updating its status.
Pre-Booking Status Workflow¶
Status transitions are automated:
- Available → Reserved: Triggered when a booked cabin is created with a preBookingId reference
- Reserved → Booked: Triggered when the associated booking status changes to "Vouchered"
Pre-Booking Management Logic¶
public List<CCabinPreBooking> managePrebookings(Integer cruiseId, Integer shipCabinId,
int targetCount, Double cost, Double price, Double commission, boolean applyPriceToAll)
Logic: 1. Load all existing pre-bookings for cruise + cabin type 2. Count non-Available (Reserved + Booked) pre-bookings 3. Validate: targetCount >= nonAvailableCount (throws CRU0200 if not) 4. Calculate desiredAvailable = targetCount - nonAvailableCount 5. If currentAvailable > desiredAvailable: delete excess Available pre-bookings 6. If currentAvailable < desiredAvailable: create new Available pre-bookings 7. If applyPriceToAll: update cost/price/commission on all existing pre-bookings
Booking Number Generation¶
Booking numbers follow the format PT-DDMM-XXXXXXXX where:
- PT is a fixed prefix
- DDMM is the current day and month
- XXXXXXXX is an 8-character random alphanumeric string generated by createId('', 8)
Generated on the frontend and passed to the API during booking creation.
Booking Totals Calculation¶
Booking totals are automatically recalculated on the frontend when cabins or add-ons change:
The updated total is saved to the booking record via the booking write API.
Cabin Capacity Validation¶
When creating or updating a booked cabin, the system validates that the number of passengers (numPax) does not exceed the cabin type's maximum passenger capacity (maxPax):
Logic:
1. Load the CShipCabin via getShipCabin(cabin.getShipCabinId())
2. If cabin.getNumPax() > shipCabin.getMaxPax() → throw CRU0207
3. Applied in both createBookedCabin() and updateBookedCabin() after validateBookingEditable()
Front-End Super-APIs¶
These aggregated APIs are designed for front-end client website consumption, providing complete cruise data with calculated retail pricing in AED.
Retail Pricing Formula¶
All prices are calculated using CruisePricingUtil:
1. Take cabin price and divide by number of passengers
2. Add all mandatory other charges (per passenger)
3. Convert to AED using exchange rate (USD = 3.67)
4. Add fixed 750 AED per passenger markup
5. Round up to nearest 100 and subtract 1 (e.g., 2754.6 → 2799)
Constants:
public static final double USD_TO_AED_RATE = 3.67;
public static final double PER_PAX_MARKUP_AED = 750.0;
public static final String RETAIL_CURRENCY = "AED";
Get All Available Cruises¶
Returns all available cruises with optional filtering by company or itinerary:
Parameters (all optional):
- companyId - Filter by company ID. If null, returns cruises from all companies.
- itineraryId - Filter by itinerary ID. If null, returns cruises from all active itineraries.
Response includes: - List of itinerary responses, each containing: - Itinerary details - Ship, area, and company information - List of cruise instances with cruise points and minimum pricing - Total cruise count across all itineraries
Get Itinerary Cruises (Internal)¶
Returns cruises for a specific itinerary with minimum pricing per passenger:
Response includes: - Itinerary details - Ship, area, and company information - List of cruise instances with: - Cruise points (route) - Minimum price per passenger in AED (cheapest available cabin) - Status and start date
Get Cruise Detail¶
Returns complete cruise details with all available cabin pricing:
Response includes: - Full cruise information - Itinerary, ship, area, and company details - Cruise points with port information - Available cabins sorted by price (ascending), each with: - Cabin and cabin type details - Calculated price per passenger in AED - Maximum passenger capacity - Mandatory charges list
Delete Validation¶
All delete operations validate referential integrity before deletion:
// Blocks if ships exist
deleteCompany(companyId)
// Blocks if itineraries or cabins exist
deleteShip(shipId)
// Blocks if itineraries exist
deleteArea(areaId)
// Blocks if ship cabins use this type
deleteCabinType(cabinTypeId)
// Blocks if other charges use this type
deleteChargeType(chargeTypeId)
// Blocks if cabin charges reference this cabin
deleteShipCabin(shipCabinId)
// Blocks if cruise templates or points use this port
deletePort(portId)
// Blocks if not Draft status or has cruises/templates
deleteItinerary(itineraryId)
// Blocks if not Draft status or has points/charges
deleteCruise(cruiseId)
Security Implementation¶
Input Validation¶
Implemented in CruiseValidationUtil.java:
// String length limits
MAX_CODE_LENGTH = 10
MAX_NAME_LENGTH = 200
MAX_DESCRIPTION_LENGTH = 2000
MAX_URL_LENGTH = 500
// Bulk operation limits
MAX_BULK_ITEMS = 100
// Numeric bounds
MIN_AMOUNT = 0.0
MAX_AMOUNT = 10,000,000.0
MAX_PASSENGERS = 20
MAX_DURATION_DAYS = 365
Validation Methods¶
| Method | Purpose | Security Check |
|---|---|---|
validateCode() |
Short identifiers | Length ≤10, alphanumeric only |
validateName() |
Name fields | Length ≤200 |
validateDescription() |
Text fields | Length ≤2000 |
validateUrl() |
URL fields | SSRF/XSS prevention |
validateBulkSize() |
List operations | ≤100 items |
validateAmount() |
Monetary values | Range and NaN checks |
validatePassengerCount() |
Pax counts | Range 0-20 |
validateDuration() |
Duration days | Range 1-365 |
validateItineraryStatus() |
Status values | Whitelist validation |
validateCruiseStatus() |
Status values | Whitelist validation |
validatePaxType() |
Charge types | Whitelist validation |
URL Validation (SSRF/XSS Prevention)¶
Protections: - Length limit (500 characters) - Protocol whitelist (http, https only) - Block dangerous schemes (javascript:, data:, file:, vbscript:) - Block internal network addresses (localhost, 127.0.0.1, 192.168., 10., etc.) - Block local hostnames (.local, .internal)
Error Message Sanitization¶
Sanitizes error messages to prevent information disclosure: - Removes SQL/database error details - Removes stack trace information - Removes file system paths - Truncates long messages
Role-Based Access Control¶
Defined in config/api-roles.properties:
| Operation | Roles |
|---|---|
list, read |
guest, agent, admin |
write |
agent, admin |
delete |
admin only |
Example configuration:
cruise/company/list=guest,agent,admin
cruise/company/read=guest,agent,admin
cruise/company/write=agent,admin
cruise/company/delete=admin
Error Handling¶
Error Codes¶
| Code | Description |
|---|---|
CRU0001 |
Itinerary not found |
CRU0002 |
Cruise not found |
CRU0003 |
Entity not found (generic) |
CRU0004 |
Duplicate code violation |
CRU0005 |
Cannot delete company with ships |
CRU0006 |
Cannot delete ship with itineraries |
CRU0007 |
Cannot delete ship with cabins |
CRU0008 |
Cannot delete area with itineraries |
CRU0009 |
Cannot delete cabin type in use |
CRU0010 |
Cannot delete charge type in use |
CRU0011 |
Cannot delete ship cabin with charges |
CRU0012 |
Cannot delete port used by templates |
CRU0013 |
Cannot delete port used by cruise points |
CRU0014 |
Cannot delete non-Draft itinerary |
CRU0015 |
Cannot delete itinerary with cruises |
CRU0016 |
Cannot delete non-Draft cruise |
CRU0017 |
Invalid itinerary status transition |
CRU0018 |
Cannot activate itinerary without templates |
CRU0019 |
Cannot delete itinerary with templates |
CRU0020 |
Cannot delete cruise with points |
CRU0021 |
Cannot delete cruise with cabin charges |
CRU0022 |
Cannot delete cruise with other charges |
CRU0023 |
Can only regenerate points for Draft cruises |
CRU0024 |
Cruise not available for booking |
CRU0025 |
At least one passenger required |
CRU0026 |
Cabin not available for cruise |
CRU0027 |
Booking not found |
CRU0028 |
Invalid booking status transition |
CRU0200 |
Target pre-booking count below non-available minimum |
CRU0201 |
Booking not found (for status change) |
CRU0202 |
Invalid booking status transition |
CRU0207 |
Number of passengers exceeds cabin maximum capacity (numPax > maxPax) |
CRU0100-CRU0118 |
Validation errors (see CruiseValidationUtil) |
Exception Hierarchy¶
Usage pattern:
try {
// Business logic
} catch (CruiseException ex) {
return new TlinqApiResponse(ex.getErrorCode(), ex.getMessage());
} catch (TlinqClientException ex) {
return new TlinqApiResponse(ex.getErrorCode(), sanitizeErrorMessage(ex.getMessage()));
}
API Reference¶
Company Endpoints¶
List Companies¶
Request:Read Company¶
Request:Write Company¶
Request:{
"session": "token",
"companyId": null, // null for create, ID for update
"companyCode": "MSC",
"companyName": "MSC Cruises"
}
Delete Company¶
Request:Itinerary Endpoints¶
List Itineraries¶
Request:{
"session": "token",
"areaId": 1,
"shipId": 1,
"minDuration": 5,
"maxDuration": 10,
"name": "optional filter"
}
Change Itinerary Status¶
Request:Get Itinerary Tree¶
Request:Cruise Endpoints¶
Write Cruise (with skeleton)¶
Request:Booking Endpoints¶
Get Availability¶
Request:Response includes: - Cruise and itinerary details - Ship and company information - Available cabins with prices - Mandatory and optional charges - Cruise points with port information
Calculate Price¶
Request:{
"session": "token",
"cruiseId": 1,
"shipCabinId": 5,
"adults": 2,
"children": 1,
"selectedOptionalChargeIds": [10, 12]
}
Response:
{
"apiStatus": {
"errorCode": "OK",
"errorMessage": "Success"
},
"apiData": {
"cruiseId": 1,
"shipCabinId": 5,
"adults": 2,
"children": 1,
"cabinCharge": 1500.00,
"cabinCurrency": "USD",
"mandatoryChargesTotal": 450.00,
"optionalChargesTotal": 200.00,
"grandTotal": 2150.00,
"lineItems": [
{
"description": "Suite Cabin",
"unitPrice": 1500.00,
"quantity": 1,
"totalPrice": 1500.00,
"currency": "USD",
"chargeType": "cabin"
},
{
"description": "Port Fees",
"unitPrice": 150.00,
"quantity": 3,
"totalPrice": 450.00,
"currency": "USD",
"chargeType": "mandatory"
}
]
}
}
Super-API Endpoints (Front-End)¶
These endpoints are optimized for front-end client websites, providing aggregated data with calculated retail pricing in AED.
Get Itinerary Cruises¶
Request:Response:
{
"apiStatus": {
"errorCode": "OK",
"errorMessage": "Success"
},
"apiData": {
"itinerary": {
"itineraryId": 5,
"itineraryCode": "MED7",
"itineraryName": "Mediterranean 7 Days",
"duration": 7,
"status": "Active"
},
"ship": {
"cruiseShipId": 2,
"shipCode": "VIRTU",
"shipName": "MSC Virtuosa"
},
"area": {
"cruiseAreaId": 1,
"areaCode": "MED",
"areaName": "Mediterranean"
},
"company": {
"companyId": 1,
"companyCode": "MSC",
"companyName": "MSC Cruises"
},
"cruises": [
{
"cruiseId": 10,
"itineraryId": 5,
"startDate": "2025-03-15",
"status": "Active",
"cruisePoints": [
{
"point": {
"cruisePointId": 50,
"stopSeq": 1,
"arrivalDatetime": "2025-03-15T16:00:00",
"departureDatetime": "2025-03-15T20:00:00"
},
"port": {
"cruisePortId": 1,
"portCode": "BCN",
"portName": "Barcelona"
}
}
],
"minPricePerPax": 2599.0,
"priceCurrency": "AED"
}
]
}
}
Get Cruise Detail¶
Request:Response:
{
"apiStatus": {
"errorCode": "OK",
"errorMessage": "Success"
},
"apiData": {
"cruiseId": 10,
"itineraryId": 5,
"startDate": "2025-03-15",
"status": "Active",
"itinerary": { ... },
"ship": { ... },
"area": { ... },
"company": { ... },
"cruisePoints": [
{
"point": {
"cruisePointId": 50,
"stopSeq": 1,
"arrivalDatetime": "2025-03-15T16:00:00",
"departureDatetime": "2025-03-15T20:00:00"
},
"port": {
"cruisePortId": 1,
"portCode": "BCN",
"portName": "Barcelona",
"country": "Spain",
"imageUrl": "https://example.com/barcelona.jpg"
}
}
],
"availableCabins": [
{
"cabin": {
"shipCabinId": 5,
"cabinCode": "INT",
"cabinName": "Interior Cabin"
},
"cabinType": {
"cabinTypeId": 1,
"typeCode": "INT",
"typeName": "Interior"
},
"charge": {
"cabinChargeId": 100,
"amount": 1000.0,
"currency": "USD"
},
"pricePerPax": 2599.0,
"priceCurrency": "AED",
"maxPax": 2
},
{
"cabin": {
"shipCabinId": 6,
"cabinCode": "OCN",
"cabinName": "Ocean View Cabin"
},
"cabinType": {
"cabinTypeId": 2,
"typeCode": "OCN",
"typeName": "Ocean View"
},
"charge": {
"cabinChargeId": 101,
"amount": 1400.0,
"currency": "USD"
},
"pricePerPax": 3299.0,
"priceCurrency": "AED",
"maxPax": 2
}
],
"mandatoryCharges": [
{
"charge": {
"otherChargeId": 200,
"amount": 150.0,
"currency": "USD",
"mandatory": true,
"paxType": "all"
},
"chargeType": {
"chargeTypeId": 1,
"chargeTypeCode": "PORT",
"chargeTypeName": "Port Fees"
}
}
]
}
}
File Locations Summary¶
| File | Path |
|---|---|
| REST API | tqapi/src/main/java/com/perun/tlinq/api/CruiseApi.java |
| Business Logic | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CruiseFacade.java |
| Pricing Utility | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CruisePricingUtil.java |
| Validation | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CruiseValidationUtil.java |
| Exception | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CruiseException.java |
| Entity Classes | tqapp/src/main/java/com/perun/tlinq/entity/cruise/C*.java |
| Entity Config | config/entities/cruise-entities.xml |
| DB Schema | config/db-changes/cruises.sql |
| API Roles | config/api-roles.properties |
| Unit Tests | tqapp/src/test/java/com/perun/tlinq/entity/cruise/CruisePricingUtilTest.java |
| Facade Tests | tqapp/src/test/java/com/perun/tlinq/entity/cruise/CruiseFacadeTest.java |
| API Tests | tqapi/src/test/java/com/perun/tlinq/api/CruiseApiIntegrationTest.java |
| Booking Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CCruiseBooking.java |
| Passenger Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CCruisePassenger.java |
| Booked Cabin Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CBookedCabin.java |
| Booking Add-on Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CBookingAddon.java |
| Pre-Booking Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/CCabinPreBooking.java |
| Charge Template Entity | tqapp/src/main/java/com/perun/tlinq/entity/cruise/COtherChargeTemplate.java |
| Booking DB Schema | config/db-changes/cruise-booking.sql |
| Booking Native Entities | tqapp/src/main/java/com/perun/tlinq/client/nts/db/cruise/Cruisebooking*.java |