Projects:Copy From Orders Refactor
Contents |
Copy from Orders Process Refactor.
The purpose of this document is to explain the functional and technical design followed in the refactoring of the "Copy From Order" process, to improve the performance and the user experience.
![]() | This feature is available starting from 3.0PR18Q1. |
Introduction.
The "Copy From Order" process had some performance problems in environments with high volumes of information, as well as some functional limitations such as not being able to select more than one order at a time and having to navigate two windows to filter/select a particular order.
To cover these deficiencies and provide a more intuitive and functional interaction, this process was migrated to a new Process Definition called “Copy From Orders”. In this document, we will explain the main functional changes that were needed to fulfill these objectives and some technical topics to a better understanding of it.
This refactor cover some of these limitations explained on the design defect https://issues.openbravo.com/view.php?id=36466 and includes other functionalities that we going to explain with more detail in this document.
Design changes.
The User Interface was affected by some changes. We can say the most important are:
- Migration to a Pick and Edit window: The process "Copy From Order" was defined as a manual process and does not make use of the benefits of a Process Definition and Pick and Edit windows. The first thing done was to migrate part of the current process logic to a new Process Definition, using the Pick and Edit (PE) pattern in those windows that currently use it, and improves interaction with users.
- Allow to select more than one order at a time: With the old process only was possible to select one order at a time, which increases the amount of user interactions to add lines of several orders. This time the new process allows select more than one order at once.
- In the grid is possible to show and filter by the following information from the orders:
- Business Partner.
- Organization.
- Date.
- Document No.
- Currency.
- Grand Total.
- Sales Trans.
- Description.
- Reference Order.
Information filtering.
With the manual process, the information was filtered by definition of the following filters: Doc No., Reference Order, Business Partner, From date, To date, Total Amount From, Total Amount To, Description.
When the Search button was pressed, then the grid was loaded with those results that matched the selected filters. Then user was able to only select a record within the grid.
To improve the user experience, the grid was replaced by another one that allows multiple selection and the most common operations on the columns (filter, sort, reorder, etc.).
With this new structure, those defined filters in the manual process are not needed anymore and the filtering of any column can be done directly on the grid. So, the input filters were removed from the window. Also, the process is simplified to avoid an extra step before select the orders to be copied.
An important functional change included in this refactor is the fact that it is not possible to select orders that don’t belong to the tree of the legal entity of the order’s organization being copied to. This new behavior is included in 3.0PR18Q1 release.
Additionally, if the PE is executed from the Sales Order window the grid is filtered by default with the sales orders of the legal entity tree of the order’s organization. Similar happens when the PE is called in the Purchase Order window, but in this case with purchase orders. In both cases the Sales Transaction column is filtered by Yes (Sales) or No (Purchases), and this filter can be cleared by the user to be able to see other orders not related to the transaction type of the order.
Rules followed when processing
The process copies information from the lines copied from and generate its own information according it header and environment settings. In this section, we going to do a summary of the most important rules followed when the process is executed:
- After the process is executed, it adds as many lines as the lines have the selected orders.
- Also, the lines are copied in the same order they were created on each order copied.
- New lines created have a reference to the line (and order) from which they were created. It is reflected on the SO/PO reference field.
- The created line takes the following information from it header and not from the line it is created: Order date, Schedule Delivery Date, Description.
- The created line has the header's business partner as BP. And if header has partner address defined then it is used as BP address or the last address created for the header's BP in other cases.
- If the Organization of the line that is being copied belongs to the child tree of the Organization of the document header of the new line, it uses the organization of the line being copied as line’s organization, else it uses the organization of the document header of the new line.
- In the case that is copied a product with attributes (an instance attribute), in this case the attribute is copied. In the case is copied a Product with attributes (not an instance attribute) or without attribute defined, in this case the attribute isn't copied.
- Created line has prices correctly computed when is copied a product to an order with price list including taxes from another not including taxes, and when is copied a product to an order with a price list doesn't including taxes from another including taxes, both ways are taken into account.
- Created line takes in account the AUM preference is enabled or not to compute the Operative/Ordered quantity and the Operative/Order UOM.
At the end of the process, a message is shown with a successful message showing how many lines have been copied or with an error message if there is any error in the process.
Technical Changes
This refactor generates several changes in the database and core java classes. In this section, we are going to do a brief explanation of the most important changes.
Database changes:
Window definition
Was created a new Pick and Execute window to improve the performance in the load of the orders.
- Name = Copy from Orders P&E
- Window Type = Pick and Execute
Also, was defined a new tab to it.
- Name = Copy from Orders
- Table = C_Order
- UI = Read Only
Defined as grid fields, similar than existing in the old “Copy From Order” manual process:
- Business Partner.
- Organization.
- Date.
- Document No.
- Currency.
- Grand Total.
- Converted.
- Sales Trans.
- Description.
- Reference Order.
Defined a new Reference
Was created a new reference for the PE window.
- Name = Copy from Orders
- Parent reference = Window Reference
And defined the window reference as:
- Window = Copy from Orders P&E
Process Definition
Was created a new process definition:
- Search Key = CopyFromOrders
- Name = Copy from Orders
- UI Pattern = Standard (Parameters defined in Dictionary)
- Handler = org.openbravo.common.actionhandler.CopyFromOrdersActionHandler
And defined the process parameters.
- DB Column Name = window
- Name = Copy lines from orders
- Reference = Window reference
- Reference Search Key = Copy from Orders
Linked column to the new Process definition:
Were updated all the buttons using the "Create From Order" manual process to use the new process definition "Create From Orders". Was modified the CopyFromPO column from the C_Order table with the following information:
- Reference = Button reference
- Reference Search Key = NONE
- Process = Button reference
- Process Definition = Copy from Orders
Java classes changes:
everal changes were done in the java classes to implements the design requirements. In this section, we will explain the created artifacts and how to extend the core’s logic by hooks.
Classes were created to separate the action handler and the processing logic, to make more easiest the understanding of the code and make the processing independent of the flows where it is used, and making possible to be used in programmatic codes like automated tests.
CopyFromOrdersActionHandler class.
The action handler class is used to execute the Copy From Orders process logic getting from parameters, the order being processed and the selected orders in the Pick and Edit window to be copied.
protected JSONObject doExecute(Map<String, Object> parameters, String content) { JSONObject jsonRequest = null; try { // Request Parameters jsonRequest = new JSONObject(content); final String requestedAction = getRequestedAction(jsonRequest); JSONArray selectedOrders = getSelectedOrders(jsonRequest); Order processingOrder = getProcessingOrder(jsonRequest); if (requestedActionIsDoneAndThereAreSelectedOrders(requestedAction, selectedOrders)) { // CopyFromOrdersProcess is instantiated using Weld so it can use Dependency Injection CopyFromOrdersProcess copyFromOrdersProcess = WeldUtils .getInstanceFromStaticBeanManager(CopyFromOrdersProcess.class); int createdOrderLinesCount = copyFromOrdersProcess.copyOrderLines(processingOrder, selectedOrders); jsonRequest.put(MESSAGE, getSuccessMessage(createdOrderLinesCount)); } }
It is important to remarks that this solution is using Weld and can be instantiated the CopyFromOrdersProcess class using dependency injection. In the next topics, we going to explain it and the java classes structure with more details.
CopyFromOrdersProcess class.
This process copies the Order Lines of the selected Orders into the Order that is being processed and provides support for hooks.
public class CopyFromOrdersProcess { @Inject @Any private Instance<CopyFromOrdersProcessImplementationInterface> copyFromOrdersProcessHooks; (...)
The copyFromOrdersProcessHooks object injects any instance of the CopyFromOrdersProcessImplementationInterface interface and executes particular logic of all of them in a sequence defined by the order field of the hook.
This logic is executed by the executeHooks() method. It iterates all injected instances of the CopyFromOrdersProcessImplementationInterface interface, sort them by the order defined on each one, and then executes each particular logic invoking the exec() method.
private void executeHooks(final OrderLine orderLine, OrderLine newOrderLine) { if (copyFromOrdersProcessHooks != null) { final List<CopyFromOrdersProcessImplementationInterface> hooks = new ArrayList<>(); for (CopyFromOrdersProcessImplementationInterface hook : copyFromOrdersProcessHooks .select(new ComponentProvider.Selector( CopyFromOrdersProcessImplementationInterface.COPY_FROM_ORDER_PROCESS_HOOK_QUALIFIER))) { if (hook != null) { hooks.add(hook); } } Collections.sort(hooks, new CopyFromOrdersHookComparator()); for (CopyFromOrdersProcessImplementationInterface hook : hooks) { hook.exec(processingOrder, orderLine, newOrderLine); } } } (...)
As reference of how to implement an specific functionality in a hook, can be found as part of the classes included on Core as part of this project. For example, the process follows the following steps: It retrieves the Order Lines of each Order that are not Discounts or are Non-Stocked BOM Products and for each one:
- Update Order and Order Line related information (implemented on UpdateOrderLineInformation.class)
- Copy product and attributes (implemented on UpdateProductAndAttributes.class)
- Calculate amounts and UOM's. (implemented on UpdateQuantitiesAndUOMs.class)
- Calculate Prices based on price list (implemented on UpdatePricesAndAmounts.class)
- Recalculate Taxes (implemented on UpdateTax.class)
As you can see, each class implements the CopyFromOrdersProcessImplementationInterface interface and executes a concrete functionality when the process is executed.
How to extend Core’s functionality with hooks.
If you want to extend the core implementation included on this refactor with your own logic, you must take into account the following.
Add the following annotations to the class:
@Dependent @Qualifier(CopyFromOrdersProcessImplementationInterface.COPY_FROM_ORDER_PROCESS_HOOK_QUALIFIER)
The class must implement the CopyFromOrdersProcessImplementationInterface interface:
public class NewHook implements CopyFromOrdersProcessImplementationInterface
Taking as example the UpdateTax hook implementation:
@Dependent @Qualifier(CopyFromOrdersProcessImplementationInterface.COPY_FROM_ORDER_PROCESS_HOOK_QUALIFIER) class UpdateTax implements CopyFromOrdersProcessImplementationInterface { private static final Logger log = LoggerFactory.getLogger(UpdateTax.class); @Override public int getOrder() { return -10; } @Override public void exec(final Order processingOrder, final OrderLine orderLine, OrderLine newOrderLine) { TaxRate tax = OBDal.getInstance().getProxy(TaxRate.class, getCurrentTaxId(newOrderLine.getProduct(), processingOrder)); newOrderLine.setTax(tax); }
When a new hook is implemented you need to override the following two methods:
- getOrder(): It will returns the order when the concrete hook will be executed. A positive value will execute the hook after the core's logic.
- exec(final Order processingOrder, final OrderLine orderLine, OrderLine newOrderLine): Executes the hook logic on the Copy From Orders process. It receives the following parameters:
- newOrder: the new order we are processing.
- oldOrderLine: the order line from which we are creating the line.
- newOrderLine: the new line created within the newOrder.
Example of a hook creation.
According to the explained above, if we want to implement a hook, for instance that updates the new order line description with the “Testing” string, and it is needed to be executed after the core actions and between other customized hooks (some with defined execution order less and greater than 10), a possible implementation of it could be:
@Dependent @Qualifier(CopyFromOrdersProcessImplementationInterface.COPY_FROM_ORDER_PROCESS_HOOK_QUALIFIER) @Override public int getOrder() { return 10; } @Override public void exec(Order processingOrder, OrderLine orderLine, OrderLine newOrderLine) { newOrderLine.setDescription("Testing"); } }
In this example it is enough to fulfill the requirements, but it can be as simple as the logic you want to execute.
Test Cases
This project has a Test Link document associated. You can see it to more references of test cases.
https://testlink.openbravo.com/testlink/linkto.php?tprojectPrefix=Communit&item=testsuite&id=40965
Performance evaluation.
Before the migration of the process, was well known that when the old Copy From Order process was running on an environment with huge amount of orders (>=150k rows) it consumed 1.5GB of heap memory and sometimes it takes too long or even didn’t finish just to open the old selector.
This problem was automatically fixed with the use of the Copy From Orders Pick and Edit, it makes the load of the grid almost instantaneous no matter the records quantities matching the filtering criteria.
Also, the user experience was improved, the UI was simplified and improved the interaction and orders filtering.