Hotellinx ERP system to Drupal Commerce integration

The task was to integrate an ERP system (more specific Hotel management system called Hotellinx) which is (and should always stay or be) a single source of truth (SSOT) for the products they have and administer. If you want to sell these products from another system like a webshop you need to transfer the data there to showcase your goods to customers. Another way of explaining that is to say the webshop is only to make a purchase but all the data is stored to the ERP system or SSOT.

Importing old content to a new website (usually only once) is called migration what is a part of every site creation. If you do importing regularly by scheduling it automatically the process is called integration. Yes, integration usually goes both ways so the sequel of the article will talk about it. Here we are going to get data from the Hotellinx API, write the result into a CSV file and import the data as commerce products (more specific product variations) into our Drupal 8 project. Below a image about the integration from which we implement bringing the data to a CSV file in this article.

Hotellinx integration architecture
Full Hotellinx ERP integration architecture



The modules we are going to need are Commerce, Commerce Feeds, Feeds and Feeds Tamper. We use Feeds for migrating the data because it is easy and appropriate for this case. Of course there are other options like custom PHP scripts, the Drupal migrate module/framework or doing it by hand.

As first step we need a custom module which downloads data from the Hotellinx API and writes the result into a CSV file.

We use a MODULE.install file with a MODULE_install() function to make it possible to change data from the drupal UI.

function MODULE_install() 
    ->set('Username', '') 
    ->set('Password', '') 
    ->set('url', 'link/to/hotellinx.api') 
    ->set('filepath', 'public://MODULE/') 
    ->set('productsFileName', 'MODULE_hotellinx_rooms.csv') 
    ->set('VariationsFileName', 'MODULE_hotellinx_variations.csv') 


Then we create an admin UI in MODULE>src>Form which enables the user to edit his Hotellinx credentials or the path of the CSV file. We need a MODULE.routing.yml to make the form accessible from the UI.


  path: '/admin/config/services/MODULE' 
    _form: '\Drupal\MODULE\Form\MigrationAdminUI' 
    _title: 'Hotellinx Settings'
    _permission: 'access administration pages'


First we add the route to the by adding


configure: MODULE.settings 


as last line. This way a link to the settings page is accessible from the install new module page.

You can add a link to the admin menu. Create file


and add


  title: 'Hotellinx Settings'
  parent: system.admin_config_services
  route_name: MODULE.settings 
  weight: 10


This enables a user to find the admin form from the module settings like API endpoint, tokens or passwords.

Now we can finally create the actual functionality. We are using hook_cron() to activate the download process.


We use PHP curl to connect to the external Hotellinx API.


$url = \Drupal::configFactory()
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_POST, true); 
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
curl_setopt($ch, CURLOPT_POSTFIELDS, $request()); 
$xmlstring = curl_exec($ch); 


The general $request() looks like this:

$Username = \Drupal::configFactory()
$Password = \Drupal::configFactory()


Which returns the responcs as it is defined in the Hotellinx API - after adding necessary and optional parameters. So replace the part <HOTELLINX POST REQUEST> with the desired API request.

The result is written into two different CSV files like this:

$file = fopen($productsPath, "w"); 
  foreach ($roomlist as $line) 
      $line .= $productMap[explode(',', $line)[0]]; 
      fwrite($file, $line . "\n"); 


According to the Feeds module documentation, all strings have to be surrounded by quotes like "string".

Hotellinx provides us with rooms and which are sold at different rates. In order to translate that into Commerce "terms" (so that it's technically practical) we need two files. The rates will be migrated as Commerce variations and the rooms will be migrated as products. Each product can be sold in different variations (e.g. Price, offer, people in it etc.).

The first variation file contains the rates and some important variables which are needed later in the integration:

"SKU","RoomTypeId",            "Title"
  104,          67,   "Single package"
  105,          68,   "Family package"
  106,          68,"Newly wed package"


The different variations are identified by the SKU. The RoomTypeId defines to which room these rates belong. Because the Feeds module cannot make this connection without external help, we need a second file for the products.


"RoomTypeId",      "Title",   "Store",  "SKU"
          67,"Single Room","my_store",    104
          68,"Double Room","my_store",105|106


This file lists the products, in this case hotel rooms and defines with a pipe as delimiter which variations belong to each product. (See SKU 105|106. The pipe is used by the module Feeds tamper, which extends Feeds.)

After running the program (execute Cron) we have two different files, one containing future commerce products and the other containing future commerce variations of those products.

The next step is migrating the data from the CSV files by using the Feeds and Commerce Feeds modules usable Commerce entities - products and variations.


Janis Bullert

Add new comment