Skip to main content

Update Availability

System Integration: PMS_API & OTA

This documentation explains the complete flow of the inventory update process (allotment), starting from the receipt of API instructions until the data is successfully stored in the delivery queue for OTA platforms.


Stage 1: Initialization & Data Injection

Repository: pms_api File: bnl/bnl_api.php Function: update_availability

The process begins when the system receives an availability update request.


Process Flow

Payload Receipt

The system receives a JSON request from the PMS API with a structure similar to the following:

{
"action": "update_availability",
"property_id": "57",
"echo_token": "7173eba4058589a6cfc216c67fl",
"values": [
{
"availability": 0,
"room_type_id": "350",
"date_from": "2026-05-23",
"date_to": "2026-05-23"
}
]
}

The request is processed through the function:

update_availability($reqjson)

The system then reads the availability value from the payload and stores it in a variable:

$final_alot

The system will save the internal calculations to the database using:

$this->utlFunc->updateDbPms_pdo(...)

Output

The final calculated values are stored in an array:

$finalUpdateArr

Formation of Final Update Array ($finalUpdateArr)

After the payload is validated, the system forms an internal data structure called:

$finalUpdateArr

This array serves as the primary payload for the subsequent OTA distribution process.

Example data structure:

$finalUpdateArr = [
[
'roomid' => '350',
'rateid' => '1234',
'keys' => [
'avail' => 0,
'minstay' => '',
'stopsell' => '',
'cta' => '',
'price' => ''
],
'date_xml_array' => [
'2026-05-23'
]
]
];

Field Descriptions:

FieldDescription
roomidInternal room ID
rateidDefault rate plan ID of the room
keys.availAvailability value to be sent to OTA
keys.minstayMinimum stay
keys.stopsellStop sell status
keys.ctaClose to arrival
keys.priceRoom price
date_xml_arrayList of update dates

This data is then sent to the XML generator process using:

$this->utlFunc->post_xmlPms_pdo(
$this->pms_name,
$this->property_id,
$finalUpdateArr,
[],
2,
1
);

Error Validation Before Distribution

Before the data is sent to the OTA layer, the system performs error validation:

if (preg_match('/errors/is', $returnResp)) {
header('HTTP/1.1 400 Bad Request');
}

If errors occur during request parsing or data validation, the system returns an HTTP 400 Bad Request.

If $finalUpdateArr is successfully formed and contains data, the system continues the distribution process:

if(is_array($finalUpdateArr) && count($finalUpdateArr)>0){
$this->utlFunc->post_xmlPms_pdo(...);
}

The post_xmlPms_pdo() function is responsible for converting internal data into an OTA XML request before passing it to the ota repository.


Stage 2: XML Payload Formation

Repository: pms_api File: common_files/ota_utilities.class.php Function: post_xmlPms_pdo()

Once the allotment value is determined, the data is converted into XML format so it can be processed by the OTA distribution engine.


Process Flow

XML Construction

The system builds an XML string containing:

  • Property information
  • Room ID
  • Allotment attribute (Alot)
  • Rate plan information
  • Restriction data

Internal Transmission

The generated XML is sent via cURL to the endpoint:

$this->ota_base_url."/common_files/createOtaUpdateRequest.php?source=$sourse&property_id=$propertyId"

This endpoint is located in the ota repository.


Stage 3: OTA Request Generation, Distribution Terminal & Routing

Repository: ota

Files:

  • common_files/createOtaUpdateRequest.php
  • utility/ota_update.php
  • utility/ota_update.class.php

At this stage, the data enters the central distribution layer to be translated, mapped, and directed to various OTA channels.


Process Flow

Generate OTA Request (createOtaUpdateRequest.php)

The file common_files/createOtaUpdateRequest.php is the initial entry point on the ota repository side, responsible for building the OTA update request based on the inventory data received from pms_api.


Initialization & Authentication

The system performs:

  • Initialization of the createOtaUpdateRequest object
  • Utility helper setup (utilfunction)
  • cURL connection setup
  • Authentication token validation

Furthermore, all incoming requests are recorded using a logging mechanism:

$createOtaUpdateRequest->utlFunc->appendLog(
'Request',
json_encode($_REQUEST)
);

Reading Request Data

The system reads the main data from the request payload:

  • property_id
  • input_array
  • ota_ids
  • Other source parameters

The input_array data contains inventory update details such as:

  • availability
  • price
  • minimum stay
  • stop sell
  • CTA / CTD
  • inclusion

The JSON payload is then converted into an internal array using:

$inputArray = json_decode($inputArrayJson, true);

Fetching Active OTA Channels

If ota_ids are not sent in the request, the system automatically fetches the list of active OTAs from the table:

external_active_status

The query used is:

SELECT ota_id_active
FROM external_active_status
WHERE property_id = '$propertyId'

This process ensures updates are only sent to active OTA channels.


Fetching Rate Plan Details

The system calls the function:

getRtDetails($inputArray)

This function retrieves rate plan and room configuration data from the tables:

  • Rooms
  • Rate_Plans

The information retrieved includes:

  • default rate
  • minimum nights
  • inclusion
  • max nights
  • cut off nights
  • last minute configuration

Calculation of Dynamic Rate & Restriction

For every date (date_xml_array), the system determines:

  • Price (price)
  • Minimum stay (minstay)
  • Inclusion
  • CTA / CTD
  • stop sell
  • allotment

If price or minimum stay is not sent, the system automatically uses the default configuration based on:

  • day of the week (Mon-Sun)
  • last minute configuration
  • roll-in period

Tax & Additional Charges Calculation

If the property has an OTA tax configuration (allowota_tax = 1), the system automatically adds:

  • tax percentage
  • fixed additional charge

based on the table:

Additional_charges

Generate OTA XML Request

Once all data has been processed, the system builds the XML request using:

$xml = new SimpleXMLElement('<request></request>');

Each date will produce a <daterange> node containing attributes such as:

  • day
  • rateplan_id
  • price
  • Alot
  • Min
  • stopsell
  • cta
  • ctd
  • maxnights
  • cutnights

Example XML generated for the payload above:

<request>
<propertyid>57</propertyid>
<otaids>...</otaids>
<callingLoc>external</callingLoc>
<daterange
day="2026-05-23"
rateplan_id="624"
price=""
Alot="0"
Min=""
stopsell=""
inclusion=""
cta=""
ctd=""
maxnights=""
cutnights=""
></daterange>
</request>

Note: The XML only contains rate plan 624 (the default rate plan for room 350). Other rate plans within the same room (4825, 69796) are not yet present — they will be added by addRestXml() in the next stage.


Build OTA Update URL

The system then constructs the destination endpoint to:

utility/ota_update.php

Distribution Terminal (ota_update.php)

The file utility/ota_update.php receives the XML request generated by createOtaUpdateRequest.php.


Bootstrap & Initialization

The system performs:

  • Utility class load
  • ota_update.class.php load
  • cURL setup
  • Logging setup

The request IP is also recorded for auditing and tracing purposes:

$request_ip = filter_input(INPUT_SERVER, 'REMOTE_ADDR');

Initial Validation (startInitial())

The startInitial() function is the primary entry point called first when ota_update.php receives a request.

function startInitial()
{
$this->current_Time();
$this->set_input();
return 1;
}

This function is responsible for:

  • Recording the submission timestamp via current_Time()
  • Delegating the entire initialization, parsing, and data preparation process to set_input()

Parsing & Setup (set_input())

set_input() is the core function that handles the entire parsing, validation, and data preparation process before the flatfile is created.

Execution sequence inside set_input():

1. Reading parameters from the request

$this->priceflag    = (int)$_REQUEST['priceflag'];
$this->alotflag = (int)$_REQUEST['alotflag'];
$this->alotflagBuff = (int)$_REQUEST['alotflag'];
$this->stopsellflag = (int)$_REQUEST['stopsellflag'];
$this->minflag = (int)$_REQUEST['minflag'];
// ...
if(isset($_REQUEST['source'])){
$this->callingLocBuffer = $_REQUEST['source']; // PMS name, e.g.: gustodian_json
}

2. Checksum validation

if ($_REQUEST['checksum'] != '1') {
$this->util_func->appendLog('set_input', "checksum value is not 1 so kill the instance
");
$this->util_func->saveOtaUpdateLog($_REQUEST['propertyid']);
exit;
}

If the checksum is invalid, the process is terminated.

3. Storing original payload in $this->reqLog

$this->reqLog      = $_POST['xml']; // original XML payload stored here
$this->buffpostxml = $_POST['xml'];

$this->reqLog is the source of truth for the Alot value from the original payload. This variable is used later in addRestXml() to ensure the Alot value sent to the OTA matches what the PMS requested, rather than the value from the database.

4. Property status validation

$this->check_Enable();
if($this->propertyStatus == 0) {
echo "Flatfile could not generate, Due to inactive property";
exit;
}

5. Completing XML with other rate plans via addRestXml()

$_POST['xml'] = $this->addRestXml();

This is a critical step. addRestXml() is called before arraymorefilter(), so $this->reqLog is already available and contains the original payload with the correct Alot value.

addRestXml() is responsible for detecting other rate plans in the same room that are missing from the payload, then completing them with data from complete_bookdetails_phil — while simultaneously overriding the Alot value to match the original payload. Detailed process is explained in the next section.

6. Parsing XML into internal array

$arrr         = $this->xml2array($inputString);
$filterdArray = $this->arraymorefilter($arrr); // tempUpdDataArr is populated here
$this->arrangeArray(); // finalUpdDataArr is populated here
$this->arrangeArray(1);

arraymorefilter() and arrangeArray() are called after addRestXml() is finished, so all rate plans — including those completed by addRestXml() — have entered tempUpdDataArr and finalUpdDataArr with the correct Alot values.


complete_bookdetails_phil Retrieves Alot from Database

Inside addRestXml(), for every rate plan not in the payload, the system hits complete_bookdetails_phil. This function calls complete_bookdetails(), which retrieves the Alot value from the database:

// complete_bookdetails() — Alot value taken from DB
$availability = $roomavailabilityqry->row('Rooms_Available_To'); // = 3 from DB
$roomcounts = $roomscount->row('rooms'); // = 0
$availability = $availability - $roomcounts; // = 3

$xmlStr .= "<daterange ... Alot="$availability" ...>"; // Alot = 3

After the response is received, other fields are cleared but Alot is not:

$current_string = preg_replace('/price\s*=\s*"(.*?)"/is',     'price=""',    $current_string);
$current_string = preg_replace('/Min\s*=\s*"(.*?)"/is', 'Min=""', $current_string);
$current_string = preg_replace('/stopsell\s*=\s*"(.*?)"/is', 'stopsell=""', $current_string);
$current_string = preg_replace('/cta\s*=\s*"(.*?)"/is', 'cta=""', $current_string);
$current_string = preg_replace('/ctd\s*=\s*"(.*?)"/is', 'ctd=""', $current_string);
$current_string = preg_replace('/inclusion\s*=\s*"(.*?)"/is', 'inclusion=""',$current_string);

As a result, $daterange contains:

<daterange day="2026-05-23" rateplan_id="4825"  Alot="3" price="" .../>
<daterange day="2026-05-23" rateplan_id="69796" Alot="3" price="" .../>

Alot Override in addRestXml()

After the while loop collects $daterange from complete_bookdetails_phil, the system retrieves the Alot value from $this->reqLog (original payload) and uses it to override all Alot values in $daterange.

This fix applies to all PMS.

while(preg_match('/(<daterange.*?)<\/daterange>/is', $current_string, $match)) {
$daterange .= trim($match[0]);
$current_string = $this->after($match[1], $current_string);
}

// Get alot from original payload XML (reqLog), not from DB
$payloadAlot = 0;
if (preg_match('/Alot\s*=\s*"(.*?)"/is', $this->reqLog, $alotMatch)) {
$payloadAlot = $alotMatch[1]; // take value from original payload
}
$daterange = preg_replace(
'/Alot\s*=\s*"(.*?)"/is',
'Alot="' . $payloadAlot . '"',
$daterange
);
$this->util_func->appendLog('addRestXml',
"{$this->callingLocBuffer} override Alot={$payloadAlot} in daterange
");

Why use $this->reqLog?

Because $this->reqLog already contains the original XML payload when addRestXml() is called, whereas $this->tempUpdDataArr is not yet populated (it is only populated after arraymorefilter() is called):

// Sequence in set_input():
$this->reqLog = $_POST['xml']; // original payload, already exists ✓
$_POST['xml'] = $this->addRestXml(); // reqLog is read here
$filterdArray = $this->arraymorefilter($arrr); // tempUpdDataArr populated here

Final Result

The final XML entering arraymorefilter():

<!-- From original payload (rate 624) -->
<daterange day="2026-05-23" rateplan_id="624" Alot="0" price="" .../>

<!-- From complete_bookdetails_phil, already overridden (rate 4825) -->
<daterange day="2026-05-23" rateplan_id="4825" Alot="0" price="" .../>

<!-- From complete_bookdetails_phil, already overridden (rate 69796) -->
<daterange day="2026-05-23" rateplan_id="69796" Alot="0" price="" .../>

Thus, tempUpdDataArr and finalUpdDataArr are populated correctly:

// tempUpdDataArr — all rate plans Alot = 0
$this->tempUpdDataArr[624]['2026-05-23'] = "##0##..."; // ✓
$this->tempUpdDataArr[4825]['2026-05-23'] = "##0##..."; // ✓
$this->tempUpdDataArr[69796]['2026-05-23'] = "##0##..."; // ✓

// finalUpdDataArr — all rate plans Alot = 0
$this->finalUpdDataArr["2026-05-23##2026-05-23"][624] = [{alot: 0}]; // ✓
$this->finalUpdDataArr["2026-05-23##2026-05-23"][4825] = [{alot: 0}]; // ✓
$this->finalUpdDataArr["2026-05-23##2026-05-23"][69796] = [{alot: 0}]; // ✓

Routing Update (sendUpdate())

The sendUpdate() function determines the type of update to be processed, such as:

  • availability update
  • price update
  • restriction update
  • reservation synchronization

Room Mapping Validation (otaMappedCheck)

The system validates that each internal room is correctly connected to an OTA room ID on each active channel.

This process ensures that no inventory data is sent to an invalid OTA mapping.


Logging & Audit Trail

The entire process is recorded using:

appendLog()

and in the final stage, the system saves logs using:

saveOtaUpdateLog($_REQUEST['propertyid']);

Stage 4: Final Queue Formation (Queueing)

Repository: ota File: utility/ota_update.class.php Function: flatfilecreation_new()

This stage is the final process before the data is sent to third-party OTA servers.


Process Flow

XML to JSON Conversion

The XML data resulting from distribution is validated and then converted into a final JSON structure stored in the variable:

$finalArr

Flatfile Formation (parseFlatfileData_new())

Since all rate plans are already in finalUpdDataArr with the correct Alot values, all rate plans enter the flatfile with values corresponding to the payload:

Rate PlanAlot in Flatfile
BAR (624)0 ✓
ROOM ONLY (4825)0 ✓
RO (69796)0 ✓

Queue Storage (process_queue)

To maintain system performance and avoid real-time direct transmission, data is first saved to the table:

process_queue

This queue will be processed asynchronously by OTA workers.


OTA Payload Formation

At this stage, the system also:

  • Builds the final OTA payload
  • Adjusts the request structure according to the channel
  • Adds synchronization metadata
  • Determines queue priority

Audit Trail (request_details_v2)

Request details are stored in the table:

request_details_v2

for the purposes of:

  • troubleshooting
  • technical audit
  • resend process
  • synchronization debugging

Stage 5: Confirmation & Alerting

Repository: ota File: utility/ota_update.class.php Function: sendAcknowledgeMent()

This stage is used to ensure the request was successfully received by the queue system and monitoring.


Process Flow

Queue Verification

The system calls the acknowledgment URL to ensure the synchronization request was successfully received by the queue processor.


Response Validation

The system validates the response from the queue server:

  • success response
  • empty response
  • timeout
  • invalid acknowledgment

If the response is invalid, the system will mark the process as a failed synchronization.


Slack Alerting (Early Warning System)

If a synchronization failure occurs, the system automatically sends a Slack notification containing:

  • property details
  • error message
  • request information
  • synchronization failure details

The main goal of this alerting is so the operations team can immediately investigate before an inventory mismatch occurs on the OTA.


Architecture Summary

StageMain ActivityRepositoryPrimary File
InitializationReceive Payload & Authenticationpms_apibnl_api.php
CalculationInventory Calculation Logicpms_apibnl_api.php
PackagingConvert Data to XMLpms_apiota_utilities.class.php
TransformationGenerate OTA RequestotacreateOtaUpdateRequest.php
TransitReceive & Route XMLotaota_update.php
Rate Plan FixOverride Alot from Payload in addRestXml()otaota_update.class.php
QueueingInsert Data to process_queueotaota_update.class.php
MonitoringSlack Alerting & Acknowledgmentotaota_update.class.php

High-Level Flow Diagram

API Payload (availability: 0, room: 350)


update_availability()
(pms_api/bnl_api.php)
│ → Read availability from payload → $final_alot = 0
│ → Update internal DB (availability + booking)
│ → finalUpdateArr.avail = original payload (0)


post_xmlPms_pdo()
(ota_utilities.class.php)
│ → Send finalUpdateArr via cURL


createOtaUpdateRequest.php

├── Read Input Payload
├── Fetch OTA Active Channels
├── Fetch Rate Plan Details
├── Calculate Rate & Restriction
├── Generate XML: <daterange rateplan_id="624" Alot="0"/>
└── Send via cURL to ota_update.php


ota_update.php → startInitial()
│ → current_Time()
│ → set_input()


set_input()
│ → Read flags (alotflag, priceflag, etc.)
│ → Validate checksum
│ → reqLog = original XML payload (Alot="0") ← stored here
│ → Validate property status

├── addRestXml()
│ ├── Detect other rate plans (4825, 69796) not in payload
│ ├── Hit complete_bookdetails_phil → Alot = 3 (from DB)
│ ├── Override Alot = 0 (from reqLog = original payload)
│ └── Return final XML: all rate plans Alot = 0

├── arraymorefilter() → tempUpdDataArr all rate plans Alot = 0
└── arrangeArray() → finalUpdDataArr all rate plans Alot = 0


sendUpdate() → otaMappedCheck()


flatfilecreation_new()

├── parseFlatfileData_new()
│ ├── Rate 624: otaalot = 0
│ ├── Rate 4825: otaalot = 0
│ └── Rate 69796: otaalot = 0
├── Insert into process_queue
└── Save request_details_v2


sendAcknowledgeMent()

├── Queue Confirmation
└── Slack Alerting if failed