Skip to content

Cruise Offer Management - Backend Implementation

This document details the backend implementation of the Cruise Offer Management module for TQPro (TourLinq Professional).

Table of Contents

  1. Architecture Overview
  2. Database Schema
  3. Entity Classes
  4. API Layer
  5. Business Logic (Facade)
  6. Front-End Super-APIs
  7. Security Implementation
  8. Error Handling
  9. API Reference
  10. 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:

{
  "session": "user-session-token",
  "fieldName": "value",
  ...
}

Success Response:

{
  "apiStatus": {
    "errorCode": "OK",
    "errorMessage": "Success"
  },
  "apiData": { ... }
}

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: - DraftActive (requires at least one cruise template) - ActiveSuspended - SuspendedActive - Active/SuspendedArchived (if no future cruises)

public CCruiseItinerary changeItineraryStatus(Integer itineraryId, String newStatus)

Cruise Status

┌─────────┐     activate      ┌─────────┐
│  Draft  │ ────────────────► │  Active │
└─────────┘                   └────┬────┘
                    ┌──────────────┼──────────────┐
                    │                             │
                    ▼                             ▼
              ┌───────────┐                ┌───────────┐
              │ Cancelled │                │ Completed │
              └───────────┘                └───────────┘
              (≥7 days before)             (after end date)

Valid Transitions: - DraftActive - ActiveCancelled (must be at least 7 days before departure) - ActiveCompleted (only after cruise end date)

public CCruise changeCruiseStatus(Integer cruiseId, String newStatus)

Special Operations

Cruise Skeleton Creation

Creates a cruise with all points, cabin charges, and other charges pre-populated:

public CCruise createCruiseSkeleton(Integer itinId, Date startDate)

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:

public List<CCruisePoint> regenerateCruisePoints(Integer cruiseId)

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:

public List<CItineraryTreeNode> getItineraryTree(String companyCode, Integer areaId, String status)

Booking Interface

Availability Check

Returns complete cruise details for booking:

public CCruiseAvailability getCruiseAvailability(Integer cruiseId)

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 pendingAvailability pending, Payment pending, Cancelled-NonRefundable - Availability pendingPayment pending, Cancelled-NonRefundable - Payment pendingVoucher pending, Cancelled-NonRefundable, Cancelled-RefundPending - Voucher pendingVouchered, Cancelled-RefundPending - VoucheredCancelled-RefundPending - Cancelled-RefundPendingCancelled-Refunded

public CCruiseBooking changeBookingStatus(Integer bookingId, String newStatus)

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

Available → Reserved → Booked

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:

totalAmount = sum(bookedCabins.price) + sum(addons.amount)

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):

private void validateCabinPaxCapacity(CBookedCabin cabin) throws TlinqClientException

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:

public CAllCruisesResponse getAllAvailableCruises(Integer companyId, Integer itineraryId)

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:

public CItineraryCruisesResponse getItineraryCruises(Integer companyId, Integer itineraryId)

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:

public CCruiseDetailResponse getCruiseDetail(Integer cruiseId)

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)

public static String validateUrl(String fieldName, String value)

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

public static String sanitizeErrorMessage(String message)

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

TlinqClientException
    └── CruiseException

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

POST /cruise/company/list
Request:
{
  "session": "token",
  "companyCode": "optional filter",
  "companyName": "optional filter"
}

Read Company

POST /cruise/company/read
Request:
{
  "session": "token",
  "companyId": 1
}

Write Company

POST /cruise/company/write
Request:
{
  "session": "token",
  "companyId": null,  // null for create, ID for update
  "companyCode": "MSC",
  "companyName": "MSC Cruises"
}

Delete Company

POST /cruise/company/delete
Request:
{
  "session": "token",
  "companyId": 1
}

Itinerary Endpoints

List Itineraries

POST /cruise/itinerary/list
Request:
{
  "session": "token",
  "areaId": 1,
  "shipId": 1,
  "minDuration": 5,
  "maxDuration": 10,
  "name": "optional filter"
}

Change Itinerary Status

POST /cruise/itinerary/changeStatus
Request:
{
  "session": "token",
  "itineraryId": 1,
  "status": "Active"
}

Get Itinerary Tree

POST /cruise/itinerary/tree
Request:
{
  "session": "token",
  "companyCode": "optional",
  "areaId": 1,
  "status": "Active"
}

Cruise Endpoints

Write Cruise (with skeleton)

POST /cruise/cruise/write
Request:
{
  "session": "token",
  "itineraryId": 1,
  "startDate": "2025-03-15",
  "generateSkeleton": true
}

Booking Endpoints

Get Availability

POST /cruise/booking/availability
Request:
{
  "session": "token",
  "cruiseId": 1
}

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

POST /cruise/booking/calculatePrice
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

POST /cruise/itinerary/cruises
Request:
{
  "session": "token",
  "companyId": 1,
  "itineraryId": 5
}

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

POST /cruise/detail
Request:
{
  "session": "token",
  "cruiseId": 10
}

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