View source | Discuss this page | Page history | Printable version   

Modules:OMS Engine/OMS Developers Guide

Contents

OMS Developers Guide

About this document

This document is divided into two main sections. The first one shows the way to call the OMS Engine from external systems through a web service, describing the input and output formats expected by the engine.

The second one explains how to develop new algorithms that can be consumed by the OMS engine.


OMS Engine as Web Service

Here you can find the documentation of the OMS Web Service API: https://livebuilds.openbravo.com/retail_modules_pgsql_pi/api?urls.primaryName=oms1.1.0

URL

The OMS engine is exposed through a Web Service with the URL:

https://<host>:<port>/openbravo/ws/org.openbravo.oms.getOrdersProposal

Request format

Version 1.0.0

This Web Service receives a JSON request as input which describes the ticket from which we want to calculate the order proposal sourcing. The structure is a simplification of the current JSON object generated by the Openbravo POS software, and it’s a common format shared by other modules, like for example the Discounts engine.

Let’s see an example:

 
{  
   "ticket":{
      "id":"XXXXXXX1",
      "organization":{
         "id":"DC206C91AA6A4897B44DA897936E0EC3",
         "_identifier":"F&B España - Región Sur"
      },
      "businessPartner":{
         "id":"ABD91C9D3BC94175B876FBBE9CACA008",
         "_identifier":"VBS Customer"
      },
      "date":"2019-07-30T11:54:21.018Z",
      "lines":[  
         {  
            "id":"000000000000010",
            "product":{
               "id":"DA7FC1BB3BA44EC48EC1AB9C74168CED",
               "_identifier":"Cerveza Ale 0,5L"
            },
            "qty":10,
            "price":100,
            "uom":100
         },
         {  
            "id":"000000000000020",
            "product":{
               "id":"C0E3824CC5184B7F9746D195ACAC2CCF",
               "_identifier":"Cerveza Lager 0,5L"
            },
            "qty":10,
            "price":100,
            "uom":100
         },
         {  
            "id":"000000000000030",
            "product":{
               "id":"7206FAA45A3842659D93F59CCA2B0613",
               "_identifier":"Cola 0,5L"
            },
            "qty":10,
            "price":100,
            "uom":100
         }
      ]
   }
}

The mandatory fields for the OMS engine are:


All the above fields are the mandatory ones when calling to the OMS engine. Other fields like _identifier or price are not really required and they will be silently ignored by the engine.

Version 1.1.0

This version includes the following modifications:

  1. The components BusinessPartner, Organization and Product can be identified from the id (like in version 1.0.0) or by the searchKey (new in version 1.1.0). It's mandatory to specify at least any of them.
  2. There is no need to include uom in the input json. In this case the quantity (qty) is expressed in the product's UOM.

Let's see an example for a valid request for version 1.1.0:

 
{  
   "ticket":{  
      "id":"TestData_Input_1.1.0",
      "organization":{  
         "searchKey":"F&B España - Región Sur"
      },
      "lines":[  
         {  
            "product":{  
               "searchKey":"ES\/0001"
            },
            "qty":1,
            "uom":100
         },
         {  
            "product":{  
               "searchKey":"ES\/0002",
               "_identifier":"Cerveza Lager 0,5L"
            },
            "qty":10
         },
         {  
            "product":{  
               "id":"7206FAA45A3842659D93F59CCA2B0613",
               "searchKey":"ES\/0019",
               "_identifier":"Cola 0,5L"
            },
            "qty":1
         }
      ]
   }
}

Response format

Version 1.0.0

The response is also formatted as JSON with the following structure:

 
{ 
  "oms":{ 
    "tickets":[ 
      { 
        "client":"23C59575B9CF467C9620760EB255B389",
        "organization":"DC206C91AA6A4897B44DA897936E0EC3",
        "lines":[ 
          { 
            "product":{ 
              "id":"C0E3824CC5184B7F9746D195ACAC2CCF",
              "_identifier":"Cerveza Lager 0,5L"
            },
            "qty":6,
            "uom":"100"
          }
        ],
        "warehouse":"5848641D712545C7AE0FE9634A163648"
      },
      { 
        "client":"23C59575B9CF467C9620760EB255B389",
        "organization":"E443A31992CB4635AFCAEABE7183CE85",
        "lines":[ 
          { 
            "product":{ 
              "id":"7206FAA45A3842659D93F59CCA2B0613",
              "_identifier":"Cola 0,5L"
            },
            "qty":10,
            "uom":"100"
          },
          { 
            "product":{ 
              "id":"C0E3824CC5184B7F9746D195ACAC2CCF",
              "_identifier":"Cerveza Lager 0,5L"
            },
            "qty":4,
            "uom":"100"
          },
          { 
            "product":{ 
              "id":"DA7FC1BB3BA44EC48EC1AB9C74168CED",
              "_identifier":"Cerveza Ale 0,5L"
            },
            "qty":10,
            "uom":"100"
          }
        ],
        "warehouse":"B2D40D8A5D644DD89E329DC297309055"
      }
    ]
  }
}
 


Everything returned by the OMS engine is wrapped into the oms namespace. Inside it we can find the tickets array, which includes all the tickets proposals calculated by the OMS engine. It’s important to note that the tickets are grouped by organization and warehouse, so it is easy to consume them by external services.


In the example above the OMS engine has proposed 2 orders: the first one with only one line with 6 units of Cerveza Lager, and the rest of the products will be delivered with a second order by a different organization and warehouse.

Version 1.1.0

The response in version 1.1.0 includes the following modifications:

  1. The components Client, Organization and Warehouse are components identified by the id, _identifier and searchKey.
  2. Added obomsRunId in the response, so the consumer can use it to retrieve more information from this window.

Let's see an example of response for version 1.1.0:

 
{ 
  "oms":{ 
    "obomsRunId": "01948CAC5011452CB13A794C722B31AE",
    "tickets":[ 
      { 
        "client":{ 
          "id":"23C59575B9CF467C9620760EB255B389",
          "searchKey":"F&B International Group",
          "_identifier":"F&B International Group"
        },
        "organization":{ 
          "id":"DC206C91AA6A4897B44DA897936E0EC3",
          "searchKey":"F&B España - Región Sur",
          "_identifier":"F&B España - Región Sur"
        },
        "lines":[ 
          { 
            "product":{ 
              "id":"DA7FC1BB3BA44EC48EC1AB9C74168CED",
              "searchKey":"ES\/0001",
              "_identifier":"Cerveza Ale 0,5L"
            },
            "qty":1,
            "uom":"100"
          },
          { 
            "product":{ 
              "id":"C0E3824CC5184B7F9746D195ACAC2CCF",
              "searchKey":"ES\/0002",
              "_identifier":"Cerveza Lager 0,5L"
            },
            "qty":10,
            "uom":"100"
          },
          { 
            "product":{ 
              "id":"7206FAA45A3842659D93F59CCA2B0613",
              "searchKey":"ES\/0019",
              "_identifier":"Cola 0,5L"
            },
            "qty":1,
            "uom":"100"
          }
        ],
        "warehouse":{ 
          "id":"5848641D712545C7AE0FE9634A163648",
          "searchKey":"RS",
          "_identifier":"España Región Sur"
        }
      }
    ]
  }
}

How-to create new OMS algorithms

The OMS engine module includes several OMS algorithms. However the engine is prepared to easily work with new algorithms provided by external modules. In this section we will see how to develop new algorithms.


It’s assumed in this howto that you have already created a new module which will include the new algorithm. If you need more information about this step, please see How_To_Create_and_Package_a_Module


OMS Database Model

Before developing your first OMS algorithm, it’s very important to understand the database model behind the OMS engine.

When the OMS engine is run for a concrete ticket, the system automatically populates some tables with useful information that is later consumed by the OMS algorithms.

Let’s see the most important tables:

OBOMS_Run

This stores information about the concrete OMS run for a ticket. Every single table explained below will mandatory have a link to the OBOMS_Run_ID. It’s critical you make sure your algorithm always filters by the OBOMS_Run_ID

OBOMS_Run_Rule

This represents the execution of a concrete rule within a OBOMS_Run. Each OBOMS_Run might have many OBOMS_Run_Rule (depending on the OMS Configuration). It’s critical you make sure your algorithm always filters by the OBOMS_Run_Rule_ID.

OBOMS_Run_Rule_Criteria

This represents the execution of a concrete rule criteria within a OBOMS_Run_Rule (and OBOMS_Run). Each OBOMS_Run_Rule might have many OBOMS_Run_Rule_Criteria (depending on the OMS Configuration). It’s critical you make sure your algorithm always filters by the OBOMS_Run_Rule_Criteria_ID.

OBOMS_Run_OrderLine

This represents the ticket lines that are pending to find the warehouse for a concrete OBOMS_Run_Rule_ID. They are always linked to an OBOMS_Run_ID and OBOMS_Run_Rule_ID.

This table is very important for the OMS algorithms because it represents the products and quantities pending to deliver that the algorithm must try to resolve. Example:


Pending Order Lines
OBOMS_RUN_ID M_PRODUCT_ID QTYORDERED C_UOM_ID OBOMS_RUN_RULE_ID
1 A 10 100 5
1 B 15 100 5
1 C 7 100 5

OBOMS_Run_Stock_Proposed

This table stores the stock proposed by the different OMS steps in the engine. It is the input and output (based on the IsOutput column) for the algorithms. It’s linked to the OBOMS_Run_ID, OBOMS_Run_Rule_ID and OBOMS_Run_Rule_Criteria_ID. It’s critical to always filter by these entities in your algorithm.


This table is very important for the OMS algorithms because it holds the stock received as the algorithm’s input, i.e. the total available quantity in a warehouse for a product. Based on this dataset, the algorithm must build a new dataset filtering out or reordering it (using the Priority column), which will be the output.


An order usually has more than one line to deliver, and the OMS algorithms must work will all the lines at the same time. The way to group together the OMS algorithm’s input and output stock is by using the GroupId column. Example:


Stock Proposed
OBOMS_RUN_ID OBOMS_RUN_RULE_CRITERIA_ID M_WAREHOUSE_ID M_PRODUCT_ID QTYORDERED C_UOM_ID PRIORITY ISOUTPUT GROUPID OBOMS_RUN_RULE_ID
1 7 8 A 50 100 0 N 2 3
1 7 8 B 27 100 0 N 2 3
1 7 9 A 11 100 5 N 2 3
1 7 8 B 27 100 10 Y 3 3
1 7 9 A 11 100 15 Y 3 3


In the table above we can see that the OMS algorithm receives as input the GroupId = 2, containing three lines: two of them for Warehouse = 8 and one for Warehouse = 9. These lines represent the total quantity available in the warehouse for this product. It will always be greater or equal than the pending quantity line.


The OMS algorithm has generated two lines more with IsOutput = Y. Note that, in the example, the algorithm has done 2 different things:

  1. To exclude one of the proposed input stock lines
  2. To add some priority to the output records.


Please notice how the OBOMS_RUN_ID, OBOMS_RUN_RULE_CRITERIA_ID and OBOMS_RUN_RULE_ID are equal for all the records. The only thing that really changes is the GroupId (and the priority)

Declare the OMS algorithm

The very first thing we need to do is to declare into the Openbravo’s Application Dictionary the new OMS algorithm we want to develop.

So we log into the Openbravo backend as System Administrator and, inside the OMS Algorithm window, we enter a new record linked with our module with a detailed name and description.


OMS Algorithm declaration


The Final flag is important and, when set, it avoids the OMS engine to run further criterias within the rule after the one that is linked to a final algorithm. Or in other words, when the Final flag is set, no more criterias can be defined to be executed after this one within the rule.

In our example the final is unset, so more criterias could be executed afterwards if the user configures them.

Create the Java class

OMS Algorithms are written as Java classes which contain the algorithm itself, and that are executed in the server. The class must extend from OMSAlgorithm class, and it must mandatory add a @Qualifier annotation to point to the OBOMS_ALGORITHM_ID just created above. To get the ID you can just export the database and look at the new src-db/database/sourcedata/OBOMS_ALGORITHM.xml file in your module’s folder.

 
@Qualifier(algorithmId = "XXXXXXXXXXXXXXXXXXXXXXXX")
public class WarehouseDoubleAvailability extends OMSAlgorithm {
 
  @Override
  public void run() {
    // TODO algorithm business logic goes here
  }
}

As you can see we need to override the run() method. Let’s see the implementation for our algorithm (imports hidden for simplicity):

 
@Qualifier(algorithmId = "XXXXXXXXXXXXXXXXXXXXXXXX")
public class WarehouseDoubleAvailability extends OMSAlgorithm {
  @Override
  public void run() {
    calculateWarehousesThatCouldIssueDoubleQtyOfIndividualProducts();
  }
 
  private void calculateWarehousesThatCouldIssueDoubleQtyOfIndividualProducts() {
    // @formatter:off
    final String hql = "insert into OBOMS_Run_Stock_Proposed("
        + "id, client, organization, "
        + "creationDate, createdBy, updated, updatedBy, "
        + "obomsRun, groupId, obomsRunRuleCriteria, "
        + "warehouse, product, orderedQuantity, uOM, "
        + "priority, isOutput, obomsRunRule) "
          
        + "select get_uuid(), sp.client, sp.organization,  "
        + "now(), sp.createdBy, now(), sp.updatedBy, "
        + "sp.obomsRun, :outStockGroupId, sp.obomsRunRuleCriteria, "
        + "sp.warehouse, sp.product, sp.orderedQuantity, sp.uOM, "
        + "sp.priority, :isOutput, sp.obomsRunRule "
        + "from OBOMS_Run_Stock_Proposed sp "
        + "join OBOMS_Run_OrderLine ol on (sp.obomsRun.id = ol.obomsRun.id "
        + "                                and ol.obomsRunRule.id = :obomsRunRuleId "
        + "                                and ol.product.id = sp.product.id "
        + "                                and ol.uOM.id = sp.uOM.id "
        + "                                and sp.orderedQuantity >= 2 *(ol.orderedQuantity)) "
        + "where sp.groupId = :inStockGroupId "
        + "and sp.obomsRun.id = :obomsRunId ";
    // @formatter:on
 
    @SuppressWarnings("rawtypes")
    final Query insertIntoSelect = OBDal.getInstance().getSession().createQuery(hql);
    insertIntoSelect.setParameter("outStockGroupId", getOutputStockGroupId());
    insertIntoSelect.setParameter("isOutput", true);
    insertIntoSelect.setParameter("inStockGroupId", getInputStockGroupId());
    insertIntoSelect.setParameter("obomsRunId", getObomsRunId());
    insertIntoSelect.setParameter("obomsRunRuleId", getObomsRunRuleId());
    logNewLine("Inserted lines: " + insertIntoSelect.executeUpdate());
  }
}

As you can see the logic is fully implemented in HQL. This is possible thanks to the OMS database model explained above. Working directly with the database is very convenient as the RDBMS is optimized to work with high volumes in a simple way.

The same logic could have been developed using Java, but that would be slower to execute and more complex to develop.

Let’s see the details about the implementation:


So, as you can see, the OMS Engine simplifies the OMS algorithm implementation because:

Retrieved from "http://wiki.openbravo.com/wiki/Modules:OMS_Engine/OMS_Developers_Guide"

This page has been accessed 2,095 times. This page was last modified on 24 March 2020, at 09:14. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.