View source | View content page | Page history | Printable version   

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.

PreConfirmTaskHook

This hook is executed when confirming a task, just before the transaction document (Goods Shipment, Goods Movement, etc.) is created, and after a set of prevalidations and delta task creation (if necessary) have taken place.

The hook is executed for individual tasks and tasks inside a group of tasks. It will be run for every task regardless of the Inventory Transaction Type. It means that if 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 MyPreConfirmTaskHook extends PreConfirmTaskHook {
 
  // Only run for Issue DOi ITT
  @Override
  public boolean isValidHookForThisTask(final OBAWOTask task) {
    return StringUtils.equals(task.getInventoryTransactionType().getId(),
        IssueDistributionOrderIssue_ITT.INVENTORY_TRANSACTION_TYPE_ID);
  }
 
  @Override
  public void exec(OBAWOTask task) throws Exception {
    // TODO: 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 PreConfirmTaskHook, which forces to override the exec() and isValidHookForThisTask() methods. The isValidHookForThisTask() method must return true if the hook must be executed for this task. In the example it will only be run for ISS-DOi tasks. The exec() method receives as parameters the task that is about to be confirmed.

PreProcessTransactionDocumentHook

This hook is executed before a transaction document is processed. This process occurs after the PreConfirmTaskHook are executed and before the execution of PostConfirmTaskHook.

 
public class MyPreProcessTransactionDocumentHook
    implements PreProcessTransactionDocumentHook<ShipmentInOut> {
 
  @Override
  public void exec(final ShipmentInOut shipment) {
    // TODO: More code here
  }
}

The class should implement interface PreProcessTransactionDocumentHook, which makes use of generics to be properly injected in the transaction document generator. The previous example defines a hook to be executed for the Goods Shipment transaction document generator (ShipmentInOutGenerator).

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 if 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.

PostConfirmGroupOfTasksHook

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

Hooks for grouping Sales Order Lines Picking lists

Bulbgraph.png   Available from Advanced Warehouse Operations 1.3.600

The Sales Order Lines Picking process allows the user to define a Grouping By. By default the system provides the most common options, however external modules can easily add new options.

The external module must add a new List Reference inside the generatePickingGroupByReference in the Application Dictionary. After that the external module must extend the abstract class GroupSalesOrderLinesInPickingHook, implementing the required methods. This is an example of a hook that groups by business partner.

 
@Dependent
@Qualifier(GroupSalesOrderLinesInPickingHook.GROUP_LINES_BY_HOOK_QUALIFIER)
class GroupLinesByBusinessPartnerHook extends GroupSalesOrderLinesInPickingHook {
  private static final String GROUP_BY_BUSINESS_PARTNER = "BP";
  private static final String BPARTNER_JSON_PROPERTY = "businessPartner";
 
  @Override
  public List<GroupOfSalesOrderLineToGeneratePicking> exec(JSONArray orderLinesJS) {
    // Logic to group. This method must return a list of GroupOfSalesOrderLineToGeneratePicking,
    // where each one has the list of SalesOrderLineToGeneratePicking.
    // If you want to group by a property found in the JS, you can easily run
    // execGroupingByJSONProperty() method
    return execGroupingByJSONProperty(orderLinesJS, BPARTNER_JSON_PROPERTY);
  }
 
  @Override
  public String getGroupByImplementation() {
    // Search key found in the "generatePickingGroupByReference" reference list
    return GROUP_BY_BUSINESS_PARTNER;
  }
}

Please note that the picking list grouping is done by the dimensions available before launching the picking tasks generation, or in other words it uses the information available directly or indirectly in the sales order lines.

If you want to group by any dimension that depends on the generated tasks, like the Attribute, Locator From, etc, you must implement a PostCreateTasksHook that can split or group the generated Picking Lists as necessary.

Inventory Transaction Type

Warning.png   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:

Warning.png   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.

Deviations

The logic to manage Deviations can be extended or modified by external modules.

Also there are several hooks that can be implemented to modify the default behavior.

This Document describes the possible extensions and how to implement them.

AWO Front End development

Client Side hooks

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

This page has been accessed 2,629 times. This page was last modified on 11 June 2020, at 07:33. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.