Sending emails from Openbravo workflows

Sometime ago, Openbravo made that big leap by integrating their wonderful ERP with a workflow engine. The Workflow Engine of choice was Activiti. You can download the module from here. Refer to the wiki page for more background.

The integration module available on forge is based on Activiti 5.8.

Its a world of goodness – Openbravo and Activiti together. Once you and your customers start experiencing the results, there is no going back. Workflows will climb their way up from a distantly relevant “Nice to Have” to an enriching “That’s easy, lets have that” position in your scheme of ERP implementation.

While discussing Openbravo workflows for an organization, a customer asked “Can I have an email sent at this point in the flow?”. Activiti makes it easy to send out emails through email tasks. There are a number of articles on the internet, so I am not delving into email tasks now.

They are an organization that have their email hosted on a google domain. For example, if the emails were sent out using gmail, the activiti.cfg.xml would look like this:

<property name=”mailServerPort” value=”587″ />
<property name=”mailServerHost” value=”tls://smtp.gmail.com:587″ />
<property name=”mailServerDefaultFrom” value=”sender@gmail.com” />
<property name=”mailServerUsername” value=”sender” />
<property name=”mailServerPassword” value=”*******” />
<property name=”mailServerUseTLS” value=”true” />

They are an organization that have their email hosted on a google domain.

When the workflow was fired, Activiti produced error messages and it was evident that it didn’t like the TLS part of config. It turns out that Activiti 5.8 doesn’t support useTLS parameter.

TLS is addressed in this patch. We applied the patch on top of Activiti 5.8 thats shipped with the integration module and that did the trick for us, and now emails go out from Openbravo.

Advertisements

Optimus HCM – Leave approval workflow

Optmius HCM, the Human Capital Management solution from Fugo, is built on the Openbravo ERP framework. Leave approval process is workflow enabled using Openbravo-Activiti integration module.

Watch the screencast from here

Dissecting Openbravo 3.0 UI

Having been worked on Openbravo 3.0 UI for more than a year now (right from RC1) , Openbravo has leaped ahead of most contemporary ERP’s in terms of UX design and usability. And a part of the credit has to go to the community too as they were actively involved in finalizing the UX and also involved enthusiastically in tested out the RC versions and providing their feedback.

In this post, I have tried to draw limelight on the basic UI components that are being used in Openbravo and their corresponding smartclient components. This is just to provide a very high level idea on how the entire architecture is coupled together. I have tried to use a single screen shot of Openbravo interface and tried to map the base components here.

 The hierarchy can be visualized in simple terms as follows:

Exhaustive information on these components are provided in the Openbravo Wiki here.

Most of these components are defined and managed from individual files (or code) thereby making it really easy to extend or customize even the UI layer. As a simple trial I shifted the TopLayout to bottom to give it a windows (OS) like experience. Not being so acquainted with scripting languages, I was really surprised by the simplicity with which I could manage that (though finding that took me a bit longer).

I wanted to share what I found and liked about this code. I would like to be corrected in case I have mentioned any component incorrectly or mapped them in an inappropriate hierarchy. The end goal is to make sure we understand the architecture so that it can be appreciated better.

Happy Working !!!

Implementing Main View using static Java script in Openbravo

In addition to the standard UI generation framework provided by Openbravo, we can also generate our own User Interface and manage them using our own controllers or by extending the controllers provided by Openbravo. There are multiple ways of achieving this. I have provided below, one way of achieving the same, which is specially better suited when the view does not need any dynamic data.

This approach requires a javascript file and a component which can tell the javascript file path to Openbravo. For the sake of simplicity, I have done all the logic in JSP and included the JSP as the html pane in smart client. You can use your own implementation stack for the same. I have used an example module (Fugo Example – com.fugoconsulting.example(DBPrefix – FEX)) for illustrating this.

Development Steps:

Html Content:

Create a jsp file to filter all the customers and their detailed information. For example create BusinessPartner.jsp with all logic and place it in modules/com.fugoconsulting.example/web/com.fugoconsulting.example/jsp/

/**
 * JSP page to provide the List of Customers
 *
 * @author pandeeswari
 */
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="org.openbravo.model.common.businesspartner.BusinessPartner"%>
<%@page import="org.openbravo.dal.service.OBQuery"%>
<%@page import="org.openbravo.dal.service.OBDal"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
 <style type="text/css">
 table.hovertable {
 font-family: helvetica;
 font-size:13px;
 color:#000;
 padding:5px;
 border-width: 1px;
 border-color: #a9c6c9;
 border-collapse: collapse;
 }

table.hovertable th {
 background-color:#008000;
 color:#FFF;
 padding:5px;
 font-weight:bold;
 font-family: helvetica;
 border-width: 1px;
 border-style: solid;
 border-color: #a9c6c9;
 }

table.hovertable tr {
 color:#000;
 padding:5px;
 border-width: 1px;
 border-color: #a9c6c9;
 color: #008000;
 }

table.hovertable td {
 border-width: 1px;
 border-style: solid;
 font-family: helvetica;
 border-color: #a9c6c9;
 padding:5px;
 }

 .text {
 font-family : helvetica;
 font-size:12px;
 }

 .heading {
 font-family : helvetica;
 font-size : 15px;
 font-weight : Bold;
 color: #008000;
 }
 .trwithoutbg {
 color:#333333;
 font-size:12px;
 background:#FFF;
 }
 </style>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Customer Information</title>
 <p>List of customers</p>
 <table id = "customer">
 <tr>
 <th>Name</th>
 <th>Name2</th>
 <th>Business Partner Category</th>
 <th>Price List</th>
 <th>Payment Method</th>
 <th>Payment Terms</th>
 <th>Account</th>
 <th>Invoice Terms</th>
 <th>Credit Limit</th>
 </tr>
 <%
 try {
 OBQuery<BusinessPartner> businessPartner = OBDal.getInstance().createQuery(BusinessPartner.class, "customer = 'Y'");

 for(BusinessPartner customer : businessPartner.list()) {
 String name = customer.getName();
 String name2 = "";
 if(customer.getName2() != null) {
 name2 = customer.getName2();
 }
 String businessPartnerCategory = customer.getBusinessPartnerCategory().getName();
 String priceList = "";
 if(customer.getPriceList() != null) {
 priceList = customer.getPriceList().getName();
 }
 String paymentMethod = "";
 if(customer.getPaymentMethod() != null) {
 paymentMethod = customer.getPaymentMethod().getName();
 }
 String paymentTerms = "";
 if(customer.getPaymentTerms() != null) {
 paymentTerms = customer.getPaymentTerms().getName();
 }
 String financialAccount = "";
 if(customer.getAccount() != null) {
 financialAccount = customer.getAccount().getName();
 }
 String invoiceTerms = "";
 if(customer.getInvoiceTerms() != null) {
 invoiceTerms = customer.getInvoiceTerms();
 }
 String creditLimit = customer.getCreditLimit().toString();
 %>
 <tr onmouseover="this.style.backgroundColor='#D0D0D0';" onmouseout="this.style.backgroundColor='#FFF';">
 <td><%= name %></td>
 <td><%= name2 %></td>
 <td><%= businessPartnerCategory %></td>
 <td><%= priceList %></td>
 <td><%= paymentMethod %></td>
 <td><%= paymentTerms %></td>
 <td><%= financialAccount %></td>
 <td><%= invoiceTerms %></td>
 <td><%= creditLimit %></td>
 </tr>
 <%
 }
 }
 catch(Exception e) {
 System.out.println("Error:"+e.getMessage());
 }
 %>
 </table>
 </head>
 <body>
 </body>
</html>

 Static Java Script Resource:

Create a javascript file to include the jsp so that it can be rendered as a main view in openbravo. For example, create fex-business-partner-info.js and place it in modules/com.fugoconsulting.example/web/com.fugoconsulting.example/js/ .


isc.defineClass("FEX_BusinessPartnerInfo", isc.HTMLPane).addProperties({

width: '100%',
 height: '100%',
 overflow: 'visible',

contentsType: 'page',
 contentsURL: 'web/com.fugoconsulting.example/jsp/BusinessPartner.jsp',

// allow only open one tab for this tab instance
 isSameTab: function (viewId, params) {
 return viewId === this.getClassName();
 },

getBookMarkParams: function () {
 var result = {};
 result.viewId = this.getClassName();
 result.tabTitle = this.tabTitle;
 return result;
 }
});

defineClass:

The method defineClass is a smartclient method which is used to define a new java script class. It will create a javascript class with the name “FEX_BusinessPartnerInfo” as a HtmlPane. isc.HTMLPane is the smartclient class that is extended to create the html pane. Any class of smartclient can be extended like this. You can also define your own javascript class and extend the same. For instance, you can also extend the “FEX_BusinessPartnerInfo” class.

contentsType:

The contentsType property is used to specify whether the included html should be opened as a page or a fragment. In our example, since its “Page”, it will load the html as a standalone html page.

contentsURL:

The contentsURL property  is used to specify the URL of the html page which we want to load. Since we have our jsp at com.fugoconsulting.example/jsp of our module’s web directory, we need to give the path as “web/com.fugoconsulting.example/jsp/BusinessPartner.jsp.

isSameTab:

The method isSameTab is overrided to stop Openbravo from opening many instance of the same tab again and again when the menu is clicked.

View Definition:

You can define the view as follows:

  1. Log in as System Administrator
  2. Open Application Dictionary -> User Interface -> View Definition
  3. Define the view as below.

View Definition

The name of the View Implementation must be same as the java script  class name. Since we are creating the main view using static java script, we don not need to specify the template or class implementation. Instead, to make openbravo aware of our static java script, we need to register it in a global component provider. We need to create our own component provider that will act as a common resource pool for our static resources.

Component Provider:

Create a component provider FEXComponentProvider.java and place it in the directory modules/com.fugoconsulting.example/src/com/fugoconsulting/example/ as follows.


package com.fugoconsulting.example;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;

import org.openbravo.client.kernel.BaseComponentProvider;
import org.openbravo.client.kernel.Component;
import org.openbravo.client.kernel.ComponentProvider;
import org.openbravo.client.kernel.KernelConstants;

/**
 * The Example module Component Provider.
 *
 * @author pandeeswari
 */
@ApplicationScoped
@ComponentProvider.Qualifier(FEXComponentProvider.QUALIFIER)
public class FEXComponentProvider extends BaseComponentProvider {
 public static final String QUALIFIER = "FEX_COMPONENTPROVIDER";

/*
 * (non-Javadoc)
 *
 * @see org.openbravo.client.kernel.ComponentProvider#getComponent(java.lang.String,
 * java.util.Map)
 */
 @Override
 public Component getComponent(String componentId, Map<String, Object> parameters) {
 throw new IllegalArgumentException("Component id " + componentId + " not supported.");
 }

@Override
 public List<ComponentResource> getGlobalComponentResources() {
 final List<ComponentResource> globalResources = new ArrayList<ComponentResource>();
 globalResources.add(createStaticResource(
 "web/com.fugoconsulting.example/js/fex-business-partner-info.js", false));
 return globalResources;
 }

@Override
 public List<String> getTestResources() {
 return Collections.emptyList();
 }
}

We should add all the static resources in the list variable globalResources.  This is done by overriding the getGlobalComponentResources() methodAt runtime, Openbravo will get the resource path from this component provider.

Now, create a menu “Customer Information” for this main view with action as “Open View in MDI”  and choose the view implementation we just created. Build the application and restart tomcat.

  1. Login as Client
  2. Choose the menu “Customer Information”
  3. Now you should be able to see the list of customers as below.

Customer Information

This example module is hosted in Openbravo forge and can be downloaded from http://forge.openbravo.com/projects/fugoexamplesmodule

Using Business Event Handlers in Openbravo

All current technologies supports multiple layers in the Application thereby enabling us the advantage and flexibility to separate the Application layer from the Business Layer and similarly separating the Business layer from the Database Layer. Openbravo is built on a MVC Architecture that enables us to separate the data layer, the logic layer and the application layer. But do we use it to the fullest extent? In most of the current solutions, the business logic is done database dependently in Triggers and Procedures. Openbravo has provided an efficient alternate to remove this database dependency.

Business event handler is an efficient alternative to database triggers.Since the business event handlers are written using DAL, it has all the advantages of using DAL and also it becomes database independent. The business event handler gets triggered whenever save, update and delete operations are performed. The event gets triggered even before the object gets into the database because of which the changes will be committed as a single transaction with all our business logics. The DAL layer uses clean and readable coding which can be used in business event handler. The business event handler uses Weld framework which will automatically detect the all the business event handlers through the using the annotation. This makes very easy for the developers to create the business event handler in a few minutes. Its enough if we just create a DAL file which extends the class EntityPersistenceEventObserver.

package org.openbravo.erpCommon.businessHandler;
import javax.enterprise.event.Observes;
import org.apache.log4j.Logger;
import org.openbravo.base.model.Entity;
import org.openbravo.base.model.ModelProvider;
import org.openbravo.base.model.Property;
import org.openbravo.model.common.businesspartner.BusinessPartner;
import org.openbravo.client.kernel.event.EntityDeleteEvent;
import org.openbravo.client.kernel.event.EntityNewEvent;
import org.openbravo.client.kernel.event.EntityPersistenceEventObserver;
import org.openbravo.client.kernel.event.EntityUpdateEvent;
/**
* Listens to events on the {@link BusinessPartnerHandler} entity.
*
* @author Pandeeswari
*/
public class BusinessPartnerHandler extends EntityPersistenceEventObserver {
private static Entity[] entities = { ModelProvider.getInstance().getEntity(BusinessPartnerHandler.ENTITY_NAME) };
private static Logger log = Logger.getLogger(BusinessPartnerHandler.class);
@Override
protected Entity[] getObservedEntities() {
return entities;
}
public void onUpdate(@Observes
EntityUpdateEvent event) {
if (!isValidEvent(event)) {
return;
}
final Entity businessPartnerEntity = ModelProvider.getInstance().getEntity(BusinessPartner.ENTITY_NAME);
String searchKey = ((BusinessPartner) event.getTargetInstance()).getSearchKey();
String name = ((BusinessPartner) event.getTargetInstance()).getName();
final Property name2Property = businessPartnerEntity.getProperty(BusinessPartner.PROPERTY_NAME2);
event.setCurrentState(name2Property, searchKey + " - " + name);
}
public void onSave(@Observes
EntityNewEvent event) {
if (!isValidEvent(event)) {
return;
}
final Entity businessPartnerEntity = ModelProvider.getInstance().getEntity(BusinessPartner.ENTITY_NAME);
String searchKey = ((BusinessPartner) event.getTargetInstance()).getSearchKey();
String name = ((BusinessPartner) event.getTargetInstance()).getName();
final Property name2Property = businessPartnerEntity.getProperty(BusinessPartner.PROPERTY_NAME2);
event.setCurrentState(name2Property, searchKey + " - " + name);
}
public void onDelete(@Observes
EntityDeleteEvent event) {
if (!isValidEvent(event)) {
return;
}
}
}

The above program appends the searchkey and name of the business partner and updates in the name2 field in business partner whenever the value is inserted or updated. Whenever an action is performed, the getObservedEntities() Method will be called which will return the array of entities that are being observed. In short, it will give the array of entities that has business event handlers. This entity array is used to check if a certain event is targeted for this observer. Say if an entity is being updated. The getObservedEntities() method will give all the observed entities and the method isValidEvent will check whether the entity is an observed entity. If the target entity exists in the list of observed entity, then it will return true. Also the event is automatically captured by @Observes and routed correspondingly to any of the methods onUpdate, onSave, onDelete.

The old and new values of a field can be accessed using OLD and NEW Identifier in database triggers. This can be done in business event handlers using the methods, getPreviousState and getCurrentState method. The method getCurrentState will be available in all the 3 classes EntityUpdateEvent, EntityNewEvent and EntityDeleteEvent whereas the getPreviousState method will be available only in EntityUpdateEvent class. This makes sense because we can get the previous state(old values) only when the record is being updated. Similarly, we can set a new value to a property(field) using the method setCurrentState which will be available in all the 3 classes. Though it is available in all the 3 classes, it is applicable only for new and update event.

final Property name2Property = businessPartnerEntity.getProperty(BusinessPartner.PROPERTY_NAME2);
Object previousName = event.getPreviousState(name2Property);
Object currentName = event.getCurrentState(name2Property);

When we want to interrupt certain event, we can throw an exception as we raise exception in database triggers. It can be done as follows,

public void onSave(@Observes EntityNewEvent event) {
if (!isValidEvent(event)) {
return;
}
final Entity businessPartnerEntity = ModelProvider.getInstance().getEntity(BusinessPartner.ENTITY_NAME);
String searchKey = ((BusinessPartner) event.getTargetInstance()).getSearchKey();
String name = ((BusinessPartner) event.getTargetInstance()).getName();
if(searchKey != null && name != null) {
final Property name2Property = businessPartnerEntity.getProperty(BusinessPartner.PROPERTY_NAME2);
event.setCurrentState(name2Property, searchKey + " - " + name);
}
else {
String language = OBContext.getOBContext().getLanguage().getLanguage();
ConnectionProvider conn = new DalConnectionProvider(false);
throw new OBException(Utility.messageBD(conn, "C_NullValue", language));
}
}

Leave in your comments about your views on implementing business logics at the database level or at the DAL layer.

Automating Attachments process in Openbravo and the Newton’s third law of Motion

Great people at times gets it right. I love the relativity theory and his third law of motion. Being in client place and in my work place defines the theory of relativity and well it’s the Newton’s third law that inspired to do something about which I am posting now. I had to work on a Excel processing scenario where data in the form of excel was to be transmitted between Openbravo and another legacy System (I prefer to call .N**  applications legacy systems). Filling the * is left to your imagination.

For data import, I first thought to create an import process like this, but then it was a day to day transaction, so thought it was not so efficient for auditing process. So I decided to use the Openbravo Attachments. So the user uploads an excels and saves the record and I process it accordingly. Part 1 complete. The next stage was creating a report for legacy system and providing it to the user. First I generated it in a particular drive and then users would access it. That was not proving to be good due to many access issues. Then I tried to create a File Dialog reference that would save the file in the location which user specifies. But then the question arises, what if the data is lost somewhere and an user tells he never downloaded it?

Then Newton came to my rescue. The attachments process in Openbravo has a cool feature that I decided to leverage. Not only can users attach files to a record, but they can also download files from an record. So in accordance to the Newton’s third law, if I could process an excel that user had uploaded, why can’t I create an excel and attach an record it to it and let the user download it? Having it operational was the only thing that occupied my mind for about an week.

How attachment works in Openbravo:

When you attach a file to a record, there is a folder created inside the attachment folder (the attachment folder is configured while setting up the instance as mentioned here). The name of the folder will be “tableid-recordid” , where tableid is the unique id of the corresponding table and recordid is the unique id of the record you are planning to attach.Then there is a record created in the C_File table with the File Name, Record Id, TableId. This is interpreted in the application and is presented to us in the application.

This is how my record will be (it’s in Classic UI).

Once the user clicks on the user clicks the Generate Process , a file is created, a folder with the recordid and tableid is created and an entry is made is in the C_File table. Easy. Done. We have attached the excel to the record. This is how the record looks now.

Now the user can just click the pointed button and download the corresponding excel file. :).

May be it will be useful in any of your interactions with legacy systems…:)

Retrieving Openbravo properties in DAL code

Openbravo.properties is the configuration page in which we specify the source folder, the database configuration and many other configuration parameters. For more information on Openbravo.properties configuration, refer here.

There will be many cases where we will need the values of the parameters configured in the Openbravo.properties. Few cases are.

  1. When you have a native jdbc connectivity code and you do not want to change that, but bring that under a DAL code.
  2. When you have attached a file to a record and want to process that using the actual file path.
  3. When you need your current database name. 
  4. When you need your current database parameters like URL, username,password, etc
  5. When you want the current context name using which the instance will be accessed.

Now lets see how we can access the Openbravo.properties in DAL code.

  1. Import the package  org.openbravo.base.session.OBPropertiesProvider.
  2. Use thegetProperty(property name) method on the OBPropertiesProvider
  3. Pass the name of the parameter whose value you require to the getProperty() method.

 Provided below is a sample code to retrieve the source path, database name, URL, database user name and database password.


package com.fugoconsulting.XXX.erpCommon.ad_process;

 import org.apache.log4j.*;
 import org.openbravo.erpCommon.utility.OBError;
 import org.openbravo.scheduling.ProcessBundle;
 import org.openbravo.dal.service.OBQuery;
 import org.openbravo.dal.service.OBDal;
 import org.openbravo.dal.core.SessionHandler;
 import org.openbravo.base.session.OBPropertiesProvider;
 import org.openbravo.service.db.DalBaseProcess;

 /**
  *
  * @author shankar
  */
 public class ImportFile implements org.openbravo.scheduling.Process{

    private static Logger log=Logger.getLogger(ImportFile.class);

    public void execute(ProcessBundle bundle) throws Exception {
         // TODO code application logic here
         try{

           String RecordId=(String) bundle.getParams().get("XXX_Import_ID");
           log.info("Record ID "+RecordId);
           String sourcepath=      OBPropertiesProvider.getInstance().getOpenbravoProperties().getProperty("source.path");
          String dburl= OBPropertiesProvider.getInstance().getOpenbravoProperties().getProperty("bbdd.url");
         String database = OBPropertiesProvider.getInstance().getOpenbravoProperties().getProperty("bbdd.sid");
         String systemUser = OBPropertiesProvider.getInstance().getOpenbravoProperties().getProperty("bbdd.systemUser");
          String systemPassword = OBPropertiesProvider.getInstance().getOpenbravoProperties().getProperty("bbdd.systemPassword"); </span>

}

catch(Exception e){
}

}

Happy Working…