View source | View content page | Page history | Printable version   
Toolbox
Main Page
Upload file
What links here
Recent changes
Help

PDF Books
Show collection (0 pages)
Collections help

Search

Projects:Advanced Warehouse Operations/Developers Documentation

Contents

Developers Guide

About this section

This section describes the way to extend and customize the Advanced Warehouse Operations module, which is available since 3.0PR17Q4. It is oriented to Java developers with functional knowledge about this module and with strong background in modularity concepts.

Introduction

Advanced Warehouse Operations module (AWO) is a powerful module that already allows many configuration combinations that should cover most of the real life scenarios. However AWO has been designed to easily support customizations or extensions developed in external modules.

The most common extensions to be developed are related to Warehouse Algorithms. Besides AWO provides many hooks to inject new functionality if necessary.

Warehouse Algorithms

There are two types of Warehouse Algorithms: Picking (PK) and PutAway (PA). AWO includes many PK and PA algorithms but it is ready to support new ones created in external modules.

How to create a Picking Warehouse Algorithm

A PK warehouse algorithm is a Java class that receives a TaskRequirement object, which has been automatically created by a concrete InventoryTransactionTypeAlgorithm implementation, and that returns a ScrollableResults of StockProposed records.

This ScrollableResults must be ordered by the concrete business logic that we want to implement in the PK Warehouse Algorithm.

The recommended way to create the ScrollableResults is through the PK_QueryBuilder class.

Creating a PK Algorithm is in general just about providing a well defined order to the available stock that can fulfill the TaskRequirement.

The PK_QueryBuilder class hides all the complexity behind this (selecting the right Storage Details taking into account the Warehouse Definition) and allows the developer to focus only on the business logic for the algorithm.

Let’s see an example: we are going to create a new PK algorithm that gets the stock by Quantity Available Descending and Travel Sequence Ascending:

 
public class PK_QtyAvail_Desc_TravelSeq_Asc extends PK_WarehouseAlgorithm {
  private static final String hqlOrderByClause = StockProposed.PROPERTY_QUANTITY + " desc, " 
     + StockProposed.PROPERTY_STORAGEDETAIL + "." + StorageDetail.PROPERTY_STORAGEBIN + "." 
     + Locator.PROPERTY_OBAWOTRAVELSEQUENCE + " asc ";
 
  @Override
  public ScrollableResults calculateStockProposed(TaskRequirement taskRequirement) {
    return PK_QueryBuilder.createPKQueryBuilder(taskRequirement, hqlOrderByClause).getStockProposed();
  }
}

The new class extends from PK_WarehouseAlgorithm and overrides the calculateStockProposed() method, which receives a TaskRequirement object that has been automatically created before.

The algorithm just defines the desired order in the hqlOrderByClause variable and calls the PK_QueryBuilder.createPKQueryBuilder() method which is in charge of analyzing the task requirement and returns the valid stock proposed in the order provided by the algorithm.

In this concrete example we don’t need to filter out storage details records, but in that case we just need to set the HQL query and the extra parameters using the PK_QueryBuilder.setHqlWhereClause() and PK_QueryBuilder.setExtraNamedParameters() methods just before running the getStockProposed() method to return the ScrollableResults. We will see a similar example in the How to create a PutAway Warehouse Algorithm section.

That’s all from the Java side.

The last step is to declare the new PK Warehouse Algorithm in the Warehouse Algorithm window available for System Administrator:

PK Warehouse Algorithm declaration

Important to introduce the complete Java class name and declare Search Storage Detail as the Warehouse Algorithm Type. After that export the database with your module in development.

How to create a PutAway Warehouse Algorithm

A PA warehouse algorithm is a Java class that receives a TaskRequirement object, which has been automatically created by a concrete InventoryTransactionTypeAlgorithm implementation, and that returns a List of String with the valid bins (M_Locator_ID) to store the stock.

This list must be ordered by the concrete business logic that we want to implement in the PA Warehouse Algorithm.

The methodology is quite similar to the PK Warehouse Algorithms explained above.

This time let’s see a more complex example. We want to develop a PA Algorithm that propose locators which already have some stock of the same product. This is the PA_MergeWithProduct distributed with AWO:

 
public class PA_MergeWithProduct extends PA_WarehouseAlgorithm {
  private static final String hqlWhereClause;
  static {
    final StringBuilder sb = new StringBuilder();
    sb.append(" exists (select 1 ");
    sb.append(" from MaterialMgmtStorageDetail sd ");
    sb.append(" where sd.product.id = :productId ");
    sb.append(" and sd.storageBin.id = " + PA_QueryBuilder.LOCATOR + ".id) ");
    hqlWhereClause = sb.toString();
  }
 
  @Override
  public List<String> calculateLocatorToList(final TaskRequirement taskRequirement) {
    final Map<String, Object> extraParameters = createParametersMap(taskRequirement);
    addLog(taskRequirement);
    final PA_QueryBuilder paQueryBuilder = PA_QueryBuilder.createPAQueryBuilder(taskRequirement, hqlWhereClause, extraParameters);
    return paQueryBuilder.calculateLocatorsTo();
  }
 
  private Map<String, Object> createParametersMap(final TaskRequirement taskRequirement) {
    final Map<String, Object> extraParameters = new HashMap<>();
    extraParameters.put("productId", taskRequirement.getProductId());
    return extraParameters;
  }
 
  private void addLog(TaskRequirement taskRequirement) {
    logLine(AWOVerbosityLevel.DEBUG, "Query is being called by PA_MergeWithProduct with extra Parameters: Product Id [%s]", taskRequirement.getProductId());
  }
}

The class must extend from PA_WarehouseAlgorithm.

It first defines a where clause (hqlWhereClause variable) to get only storage details for the task requirement’s product. Remember that we want to propose bins which already have that product.

Please note we can refer to the outer query through the PA_QueryBuilder.LOCATOR constant (more constants available).

As you can see we need to pass the productId as a parameter, so we build the extraParameters map with just one parameter this time.

Verbosity log is available for both PK and PA Warehouse algorithms, so feel free to call the logLine() method if you need it.

Finally we use a PA_QueryBuilder to get the list of valid locators, which is quite similar to the PK_QueryBuilder for Picking algorithms.

Alternatively you can implement the PA algorithm manually, but it’s highly recommended to use the PA_QueryBuilder to reduce complexity and to ensure the proposed bins are valid according to the Warehouse Definition.

Finally, as we did for the PA Warehouse Algorithm, we need to declare the new algorithm in the Warehouse Algorithm window:

PA Warehouse Algorithm declaration

This time the Warehouse Algorithm type is Search Locator To.

Hooks

AWO provides many hooks that can be used by external modules to inject new functionality at concrete points in the AWO flow. Here is the list of hooks and how to implement them.

Hooks at task creation time

These hooks are driven by the concrete implementation of the Inventory Transaction Type Algorithm, so the developer has important context information about the task creation.

The Inventory Transaction Type Algorithms are in charge of creating TaskRequirements from an input object (Sales Order, Purchase Order, Storage Detail, etc.). The know how to analyze the input object to get the task requirements that will be automatically transformed to tasks if possible.

To implement this kind of hooks you need to define a new custom Inventory Transaction Type Algorithm implementation with a higher priority than the distributed with AWO.

The recommended, easiest and safest way to do it is by creating a new class that extends directly from the InventoryTransactionType Algorithm implementation you are working on and override two methods:

  1. getPriority() to return a priority higher than the class you’re overriding.
  2. The hook that you want to override: postCreateTaskHook() and/or postCreateTasksHook()

PostCreateTaskHook

This hook is executed just after a task is created and before it is autoconfirmed (or not) based on the routing configuration.

Let’s see a simple example:

 
@Qualifier(InventoryTransactionTypeAlgorithm.ITT_ALGORITHM_QUALIFIER)
public class MyIssueSalesOrder_ITTAlgorithm extends IssueSalesOrder_ITTAlgorithm {
  @Override
  protected int getPriority() {
    return super.getPriority() + 1;
  }
 
  @Override
  public void postCreateTaskHook(OBAWOTask task, Map<String, Object> taskRequirementExtraParameters) {
    super.postCreateTaskHook(task, taskRequirementExtraParameters);
    logLine(AWOVerbosityLevel.DEBUG, "This is run just after creating a task [%s]", task);
    // More code here
  }
}

In the example we want to add some logic just when a task related to an Issue Sales Order has just been created, so we extend directly from IssueSalesOrder_ITTAlgorithm which is the class in charge of this logic in AWO.

The @Qualifier annotation allows to detect the class by the hook manager. Do not forget to add it.

The getPriority() method is overriden to return a higher priority, so our algorithm will be used instead of the AWO’s one.

The postCreateTaskHook() is actually the method that will be executed after creating a task. It receives as parameters the task that have just been created and a Map with the task requirement extra parameters.

Important: the hook is internally used by the AWO engine itself. It is highly recommended that you call the super implementation of this method unless you know what you are doing.

Inside this method we add the logic that we want to implement. In the example we just log it in Verbosity Logger.

PostCreateTasksHook

This hook is executed just after all the tasks are created and before they are autoconfirmed (or not) as a group based on the routing configuration.

Let’s see a simple example similar to the previous one:

 
@Qualifier(InventoryTransactionTypeAlgorithm.ITT_ALGORITHM_QUALIFIER)
public class MyIssueSalesOrder_ITTAlgorithm extends IssueSalesOrder_ITTAlgorithm {
 
  @Override
  protected int getPriority() {
    return super.getPriority() + 1;
  }
 
  @Override
  public void postCreateTasksHook() {
    super.postCreateTasksHook();
    logLine(AWOVerbosityLevel.DEBUG, "This is run just after creating all the tasks");
    logLine(AWOVerbosityLevel.DEBUG, "This is the batch of tasks generated: [%s]",
             getBatchOfTasks());
    logLine(AWOVerbosityLevel.DEBUG, "And this is the input object: " + getBaseOBObject());
    // More code here
  }
}

In the example we want to add some logic just when all the tasks related to an Issue Sales Order have just been created, so we extend directly from IssueSalesOrder_ITTAlgorithm which is the class in charge of this logic in AWO.

The @Qualifier annotation allows to detect the class by the hook manager. Do not forget to add it.

The getPriority() method is overriden to return a higher priority, so our algorithm will be used instead of the AWO’s one.

The postCreateTasksHook() is actually the method that will be executed after creating a task. The method doesn’t receive any parameter, but you can easily take the batch of tasks with all the generated tasks or the input object from which the task requirements were created as shown in the example.

Important: the hook is internally used by the AWO engine itself. It is highly recommended that you call the super implementation of this method unless you know what you are doing.

Inside this method we add the logic that we want to implement. In the example we just log it in Verbosity Logger.

Hooks at task confirmation time

This kind of hooks are implemented in a different way than the previously explained at task creation time. In this case we must simply create a new class that extends from the concrete hook abstract class that we want to get hooked.

PostConfirmTaskHook

This hook is executed after confirming a task. It is important to understand that it will be executed when confirming any task regardless of the Inventory Transaction Type. It means that you want to run the hook for a concrete Inventory Transaction Type you must manually check it in your hook implementation.

Let’s see an example:

 
@ApplicationScoped
public class MyPostConfirmTaskHook extends PostConfirmTaskHook {
  private static final Logger log = LoggerFactory.getLogger(MyPostConfirmTaskHook.class);
 
  @Override
  public void exec(OBAWOTask task) throws Exception {
    OBAWO_InventoryTransactionType itt = task.getInventoryTransactionType();
    log.debug("The Inventory Transaction Type is: " + itt.getName());
    // More code here
  }
}

The @ApplicationScoped allows the class to be detected by the dependency injection engine. Do not forget to add it.

The class extends from PostConfirmTaskHook, which forces to override the exec() method. It receives as parameters the task that has been just confirmed.

Inside the method we can add any logic that we want. Even we can call the CentralBroker again if necessary.

In the example we get the Inventory Transaction Type from the task and log it using Log4J.

PostConfirmTaskHook

This hook is executed after confirming a group of tasks document (like a reception list). It is important to understand that it will be executed when confirming any group of tasks regardless of the Inventory Transaction Type.

Let’s see an example:

 
@ApplicationScoped
public class MyPostConfirmGroupOfTasksHook extends PostConfirmGroupOfTasksHook {
  private static final Logger log = LoggerFactory.getLogger(MyPostConfirmGroupOfTasksHook.class);
 
  @Override
  public void exec(BaseOBObject groupOfTasksDocument, BaseOBObject outputOBObjectHeader)
   throws Exception {
    if (groupOfTasksDocument instanceof OBAWOReceptionList) {
      OBAWOReceptionList receptionList = (OBAWOReceptionList) groupOfTasksDocument;
      log.debug("A reception/issue list have just confirmed: " + receptionList.getIdentifier());
      log.debug("Number of tasks: " + receptionList.getOBAWOTaskList().size());
      log.debug("It has generated an output document: " + outputOBObjectHeader.getIdentifier());
    }
  }
}

The @ApplicationScoped allows the class to be detected by the dependency injection engine. Do not forget to add it.

The class extends from PostConfirmGroupOfTasksHook, which forces to override the exec() method. It receives as parameters:

Inside the method we can add any logic that we want. Even we can call the Central Broker again if necessary.

In the example we just log some information in Log4J

Inventory Transaction Type

Note: this is an advanced topic that shouldn’t be necessary for basic customizations.

AWO provides a list of the most important Inventory Transaction Types (ITT) for a Warehouse. These ITTs are distributed along with its correspondent Inventory Transaction Type Algorithm.

As we have seen before in the Hook at task creation time section, these algorithms are in charge of analyzing an input entity to generate the task requirements that will be transformed to tasks by the AWO engine.

In general the ITT algorithms provided by AWO do a good job, but the AWO engine allows to modify and customize their behavior if necessary. Moreover, it is perfectly supported to add completely new Inventory Transaction Types with their algorithm implementation.

This is an important feature that transforms AWO into a very powerful framework to create any kind of task.

In this section we will see how to modify an existing ITT algorithm and how to create a new ITT with its algorithm.

Modifying an existing ITT algorithm

The main objective of modifying an existing ITT algorithm is to alter the way TaskRequirements are generated, but we can also modify other things.

The recommended way to do it is exactly the same as we saw in the Hooks at task creation time howto: to extend from the current ITT algorithm implementation that we want to modify and override the necessary methods, as we did for the postCreateTaskHook() and postCreateTasksHook() methods.

Apart from those hook methods, we can override:

Important: these methods are internally used by the AWO engine itself. It is highly recommended that you call the super implementation of each method before implementing your new stuff unless you know what you are doing.

Let’s see an example:

 
@Qualifier(InventoryTransactionTypeAlgorithm.ITT_ALGORITHM_QUALIFIER)
public class MyReceiptPurchaseOrder_ITTAlgorithm extends ReceiptPurchaseOrder_ITTAlgorithm {
  private static final Logger log = LoggerFactory.getLogger(MyReceiptPurchaseOrder_ITTAlgorithm.class);
  private Date maxDate;
 
  @Override
  protected int getPriority() {
    return super.getPriority() + 1;
  }
 
  @Override
  protected void init() {
    super.init();
    log.debug("BaseOBObject, quantity, action, user assigned, default task status and extra parameters are available");
    log.debug("Example. This is the action: " + getAction());
    maxDate = DateUtils.addDays(new Date(), -2);
  }
 
  @Override
  protected boolean validate() throws OBException {
    super.validate();
    // Return true or throw an exception
    if (order.getOrderDate().after(maxDate)) {
      throw new OBException("The order " + order.getIdentifier() + " is not elegible for task generation yet");
    }
    return true;
  }
 
  @Override
  public List<TaskRequirement> getTaskRequirements() {
    List<TaskRequirement> myTaskRequirements = new ArrayList<>();
    for (TaskRequirement taskRequirement : super.getTaskRequirements()) {
      final Product product = taskRequirement.getProduct();
      if (product.getName().startsWith("XX")) {
        logLine(AWOVerbosityLevel.WARNING, "Product [%s] is skipped for task generation", product);
      } else {
        myTaskRequirements.add(taskRequirement);
      }
    }
    return myTaskRequirements;
  }
}

In this example we are extending from ReceiptPurchaseOrder_ITTAlgorithm because we want to modify the generation of tasks for a Purchase Order reception.

The @Qualifier annotation allows to detect the class by the dependency injection manager. Do not forget to add it.

The getPriority() method is overriden to return a higher priority, so our algorithm will be used instead of the AWO’s one.

The init() method first runs the super implementation and then sets the maxDate variable that will be used later on.

The validate() method first runs the super implementation and then verifies the order date is before the maxDate calculate before and throws an exception that will stop the task generation process in that case.

The getTaskRequirements() method is also overridden. For this concrete example we call the super implementation and we skip the task requirements having a product’s name that starts with XX. If so we log it in the verbosity logger as a warning.

Creating a new ITT and ITT algorithm

Creating a new ITT is only really needed when the ITTs distributed with AWO don’t cover your needs. In this case the first thing you need to do is to define your new ITT inside the Inventory Transaction Type window available at System Administrator level.

New Inventory Transaction Type declaration

Important things to take into account:

Once we have defined the new ITT, it’s the time to create the ITT algorithm implementation that will analyze the object stored in the previously defined Table to create the necessary task requirements.

The creation of a new ITT algorithm is heavily based on what we have seen in the previous section but with some new stuff. Let’s see it:

 
@Qualifier(InventoryTransactionTypeAlgorithm.ITT_ALGORITHM_QUALIFIER)
public class MyNewITTAlgorithm extends InventoryTransactionTypeAlgorithm {
 
  @Override
  public String getInventoryTransactionId() {
    // This is the OBAWO_inv_tran_type_ID of our new ITT
    return "XXXXXXXXXXXXXXXXX";
  }
 
  @Override
  public List<TaskRequirement> getTaskRequirements() {
    // TODO Analyze and generate a List of TaskRequirement
    return null;
  }
 
  @Override
  public BaseOBObject getBaseOBObjectThatCreatedTheTask(OBAWOTask task) {
    // This is object from which the task has been created
    return task.getObdoDistOrderline().getDistributionOrder();
  }
}

The new ITT algorithm implementation must extend directly or indirectly from the abstract class InventoryTransactionTypeAlgorithm.

In the example we extend directly from this class, but it’s highly recommend to review the hierarchy tree under InventoryTransactionTypeAlgorithm and extend from any child class that is more suitable for our needs.

For example, in our new Reception ITT it would have been more suitable to extend from ReceiptOrIssue_ITT instead, because this class automatically creates a Reception/Issue list with the generated tasks.

Remember also to set the Qualifier annotation so the ITT algorithm is taken into account by the dependency injection engine.

Extending from InventoryTransactionTypeAlgorithm forces to override the following methods:

Besides those mandatory methods, you can also implement any other method explained in previous sections, like the hooks at creation time.

Retrieved from "http://wiki.openbravo.com/wiki/Projects:Advanced_Warehouse_Operations/Developers_Documentation"

This page has been accessed 27 times. This page was last modified on 2 October 2017, at 10:35. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.