How PeopleSoft Applications are tested during an Implementation

                         Once the developement is completed the Developer will do unit testing and he/she will be sending out a request to the Migration team to migrate the project to the respective testing environment, say UAT environment.
Generally, UAT will be performed by the Business/ Users / Vendor who are specialized in that particular domain.
Before all the testing starts, the testing team will analyse the product and create a test design for the module/product, based on the Business Requirement Document(BRD) given by the client, and based on the Functional Requirement Specification Document(FRSM), which basically will be created by the Development Team.
Soon after the real testing starts.
Smoke test is generally performed first with few number of test cases in each product to verify the application is ready for the testing. As a practice, testers will test some 10 to 15 % of the total test cases that they have designed. Tester may rise defect if they come across any and the developer will fix it immediately.
Comprehensive testing is a place where the each and every field and functionalities of the application/ product will be tested by the testing team. say 100% of the test cases will be tested by the them. In the course of the testing, if they find any defect they will immediately rise an issue and the development team will fix it in a short span of time.
So, at the end of the comprehensive round the product will come to a shape.
Regression testing is the final round of testing to make sure the product is a fool-proof, This testing will always be done in the code freeze environment, the developer will have no access to the environment in the mean time. Testing team will take some 60% of the test cases that they have tested in the comprehensive round.
In worst case, if the testing team come across any defect then they will rise it with a show stopper and issues will be addressed with a high priority. Now regression will be done to make sure that the code change doesn’t have any impact.
Some critical test cases will be taken for final sanity round and then the product will be given to user for UAT signoff and testing.

Advertisements

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

PS_APP_HOME introduced in PeopleTools 8.52

Traditionally, a PS_HOME contained both PeopleTools and Application related files. This meant that you had to maintain four different PS_HOME folders for each of you DEV, TST, QA and PROD environments. For customers that use more than one PeopleSoft application (and there are many many), the number of PS_HOMEs multiply and get un-manageable at some point in time.

PeopleTools 8.52 introduces PS_APP_HOME which co-exists with a PS_HOME, ofcourse along with the PS_CFG_HOME. That sounds like a lot of HOMEs 😉 What does each of these contain? Simply put, PS_HOME – contains all that your PeopleTools CDs offer, while PS_APP_HOME contains all that your application and language media packs offer. PS_CFG_HOME contains all your application server and process scheduler domain configuration fules.

I personally think that the separation offered by PS_HOME and PS_APP_HOME reduces a lot of administration overhead. And a lot of space too. The separation also tremendously streamlines the application of peopletools patches and promises a shorter time-to-production.

Just that you need to ensure you set PS_APP_HOME to the appropriate environment (DEV/QA etc) before invoking PSADMIN. You would also need to enlist both PS_HOME and PS_APP_HOME in your process scheduler for SQR files, because DDDAUDIT would come from PS_HOME and PER010 from you HR PS_APP_HOME.

Finally, PS_APP_HOME is an optional feature. You could still continue to use PS_HOMEs the traditional way

Write Data Between Table And File in Postgres

Write Data between Table and File

Postgres offers COPY commands for writing table data into text files (ascii or binary) and also write back from these files to postgres table.

The COPY TO command is used to copy the content of a table to a standard text file. If you specify column name of a table, while using COPY TO, the specified fields only will be written to a file. COPY FROM command is used to copy the content of a file to a database table.

Copy Data from Table to File

The following command is used to copy data from table to file.

Syntax
COPY <tablename> [(<column list,...>)] TO {'<filename>'}

where

tablename - Name of the table that we are going to copy
Column List - Column Names of the given table
File Name - Specify the file to which data should be written to

Example

Copy All fields to file

 postgres=#COPY student TO '/tmp/stud1.txt';

The above command will copy all the fields from student table to stud.txt file.

Copy Specified Field Only

postgres=#COPY student(rollno,total,avg) TO '/tmp/stud2.txt';

The above command will copy only specified fields from the student table to the stud.txt file and the file is saved in /tmp/ directory.
OutPut  
Sample Files


Copy Data from File to Table

The following command is used to copy data from File to Table.

Syntax

COPY <tablename> [(<column list,...>)] FROM {'<filename>'}

where

tablename -Name of the table that we are going to copy
Column List - Column Names of the given table
File Name - Specify the file name from which data will be copied to table.

Example
Copy All fields to Table

 postgres=#COPY student FROM '/tmp/stud1.txt';

The above command will copy the file named stud1.txt data to the student table.

Copy Only Speicified fields to Table

 postgres=#COPY student(name,total,avg) FROM '/tmp/stud2.txt'; 

The above command will copy data from the given file to the specified fields in student table.

Output

See More in http://www.postgresql.org/docs/8.1/static/sql-copy.html

Tablespace information in Oracle

Every DBA evolves his/her own style over a period of time. They build handy scripts  to carry out each and every activity in the database, including monitoring jobs, gathering tablespace information etc etc. Here is one particular script that I use to gather free/utilized space information on tablespaces.

SELECT A.TABLESPACE_NAME, A.TOTALSPACE TOTALSPACE,(A.TOTALSPACE - B.FREESPACE) USEDSPACE, B.FREESPACE FREESPACE FROM
 (SELECT TABLESPACE_NAME,SUM/1024/1024/1024 TOTALSPACE FROM DBA_DATA_FILES where TABLESPACE_NAME in (select tablespace_name from dba_segments where segment_name in ('SET OF TABLES T1','T2','T3'.'T4'))GROUP BY TABLESPACE_NAME) A,
 (SELECT TABLESPACE_NAME,SUM/1024/1024/1024 FREESPACE FROM DBA_FREE_SPACE where TABLESPACE_NAME in select tablespace_name from dba_segments where segment_name in ('SET OF TABLES T1','T2','T3'.'T4')) GROUP BY TABLESPACE_NAME) B WHERE A.TABLESPACE_NAME =B.TABLESPACE_NAME ;

Sample Output for the above script will be

TABLESPACE_NAME       TOTALSPACE        USEDSPACE       FREESPACE
GPAPP                                           101                      92.4963989       8.50360107
PSINDEX                                      167                     150.486206       16.5137939

Hope it will be useful for you. Comments are most welcome.

“Appreciation is a wonderful thing:It makes what is excellent in others belong to us as well”

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.