Developing iDempiere 4: Create a new class model with window and tabs

From iDempiere en
Jump to navigation Jump to search

Introduction

iDempiere can be extended by creating new class models for specific business requirements without editing code in the trunk so that your bespoke development can be protected from system upgrades.

This tutorial will show you how to create a parent-child class model and a window with tabs and fields to create, update and delete data using the built-in functionality of iDempiere.

It is based on this video by Jan Thielemann from evenos GmbH.

The process is as follows:

  • Create the underlying database tables to support the new class model;
  • Create Table entries in the iDempiere dictionary to register the database tables;
  • Create a Window and Tab for the parent table, with a Sub-Tab for the child table;
  • Create a Menu entry to open the Window;
  • Build a plug-in so that the new functionality can be deployed with the base application.

Prerequisites

The tutorial assumes that you have set up iDempiere in an Eclipse development environment. If you have not, follow these instructions for installing iDempiere for Development:

  1. Install Development Prerequisites
  2. Download the Code
  3. Setting up Eclipse
  4. Importing DB Seed Manually
  5. Running iDempiere within Eclipse

The tutorial also assumes that you know how to create PostgreSQL tables using a SQL editor such as the psql command line interface or a tool like pgAdmin3.

New class model

The first step is to create a new class model in the form of two database tables with a parent-child relation between them.

Parent table

Create the main parent table eve_main as follows:

CREATE TABLE eve_main(
-- Mandatory columns
  eve_main_id numeric(10,0) PRIMARY KEY,
  eve_main_uu character varying(36) DEFAULT NULL::character varying UNIQUE,
  ad_client_id numeric(10,0) NOT NULL,
  ad_org_id numeric(10,0) NOT NULL,
  isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
  created timestamp without time zone NOT NULL DEFAULT now(),
  createdby numeric(10,0) NOT NULL,
  updated timestamp without time zone NOT NULL DEFAULT now(),
  updatedby numeric(10,0) NOT NULL,

-- User-defined columns
  value character varying(40) NOT NULL,
  name character varying(255) NOT NULL,
  description character varying(255) NOT NULL,
  help character varying(2000),
  m_product_id numeric(10,0),
  UNIQUE (ad_client_id, value)
);

The iDempiere table naming convention is to prefix the table name with three or more letters specific to your project, followed by an underscore and the table name in camelCase. In this example, the table name is prefixed with eve_, for the name of the company that Jan works for, Evenos.

The table must start with a set of mandatory columns that are used by iDempiere to manage the records and record activity for audit purposes. In the table above they are:

  • eve_main_id: the primary key of the table, named by adding the suffix _id to the table name;
  • eve_main_uu: a unique identifier, named by adding the suffix _uu to the table name;
  • ad_client_id: the identifier of the client or tenant;
  • ad_org_id: the identifier of the organisational unit within the client;
  • isactive: records can be deactivated to allow for logical deletes;
  • created: the date this record was created;
  • createdby: the user that created the record;
  • updated: the date this record was updated;
  • updatedby: the user that updated the record.

The recommended user-defined columns include:

  • value: the search key or code;
  • name: the display name;
  • description: long description;
  • help: help or comments.

The parent table includes a field m_product_id which is in the standard iDempiere table M_Product, and it has a constraint that ensures that the contents of the client and value columns are unique in combination.

Note that columns with the 'NOT NULL' constraint will become mandatory fields in the window.

Child table

Create the child table eve_sub as follows:

CREATE TABLE eve_sub(
-- Mandatory columns
  eve_sub_id numeric(10,0) PRIMARY KEY,
  eve_sub_uu character varying(36) DEFAULT NULL::character varying UNIQUE,
  ad_client_id numeric(10,0) NOT NULL,
  ad_org_id numeric(10,0) NOT NULL,
  isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
  created timestamp without time zone NOT NULL DEFAULT now(),
  createdby numeric(10,0) NOT NULL,
  updated timestamp without time zone NOT NULL DEFAULT now(),
  updatedby numeric(10,0) NOT NULL,

-- User-defined columns
  eve_main_id numeric(10,0) NOT NULL references eve_main(eve_main_id),
  value character varying(40) NOT NULL,
  name character varying(255) NOT NULL,
  description character varying(255) NOT NULL,
  help character varying(2000),
  UNIQUE (ad_client_id, value)
);

This is just like the parent table, except it has a foreign key relationship to the parent.

Table and Columns

Login as SuperUser

The window for the new class models is created by configuring iDempiere to use the new tables, using the SuperUser login and System client, so browse to http://localhost:8080/webui/ and login as SuperUser and check the Select Role box:

GS01 Login Select Role.png


Make sure the Client is System and the Role is System Administrator:

GS01 Role.png

Create the Table and Columns

After login, select the create Table and Column button on the right of the Table and Column entry in the Favourites window:

GS01 Table and Columns.png


Create the Table by entering the parent database table in the DB Table Name field:

GS01 Create Main.png

Note:

  • the prefix and the table name have been capitalized so that when we build the plug-in, the iDempiere Model Generator generates Java classes with names that conform to the standard naming convention;
  • the Database Access Level is set to 'All' to ensure that the table can be used by all Clients, including System;
  • the Entity Type is 'User maintained' to protect the table from system upgrades.

See the Table and Column documentation for the usage of the other table attributes: Template:Table and Column (Window ID-100 V1.0.0)#TAB:_Table


Save the Table record, then click on the Processes cog to create columns from the database table:

GS01 Create Columns.png


The process will ask you to confirm that you want to create the columns:

GS01 Create Database Columns.png


When the process has completed it will list the columns that were created:

GS01 Create Columns Success.png


Now you have the new Table and Columns to work with:

GS01 Table and Columns Created.png


Scroll through the Columns listed in the grid below and change their properties as required by the application. As an example, click on the Edit button on the left of the record for M_Product_ID to open up the details of the column:

GS01 Column Details.png

Note that M_Product_ID is already in the system dictionary because it is a column in the table M_Product so some of the column attributes are already populated.

See the Table and Column documentation for the usage of each of the other Column attributes: Template:Table and Column (Window ID-100 V1.0.0)#TAB:_Column. The video has a detailed discussion of all the various Column attributes.


Now repeat the exercise to create a Table entry for the child database table eve_sub and generate the columns from the database:

GS01 Child Table and Columns Created.png


We must link the child table to its parent, using the column EVE_Main_ID, so select it and open it, then check the Parent link column attribute and save the change to the column:

GS01 Link Child to Parent.png

Note that we could have done the same to the parent table EVE_Main above to link it to the M_Product table too.

Window and Tabs

We have created the entries for the two tables, so now we can create the window and tab for the parent EVE_Main and a sub-tab for the child EVE_Sub.

Create Window

Click on the button to create a new window (on the right of the Window, Tab & Field entry in Favourites) and enter the name.

Uncheck the attribute Sales Transaction because it is not relevant, and save the record:

GS01 Create Window.png

Create Tab

Click on the Tab tab under the window you created above to open a new screen for the tab.

Enter the name Main Tab, and select the Table name EVE_Main_Evenos Main from the dropdown.

Check the attribute Single Row Layout and then save the record:

GS01 Create Tab.png


Scroll down to the large Create Fields button on the tab screen and select it to generate the fields from the database table. Select OK on the confirmation screen:

GS01 Create Tab Fields.png


When the process has completed it will list the fields that were created, and after you dismiss the popup confirmation, you will see them at the bottom of the tab screen:

GS01 Create Tab Fields Created.png


You can click on the Field Sequence tab to adjust the field ordering.


Click on the cog in the toolbar to open a graphical editor to edit the layout of the fields:

GS01 Tab Editor Process.png


The Tab Editor will open in a new window where you can add and remove fields, drag them around (by grabbing the field title), resize them (by double-clicking with and without holding down CTRL), and so on:

GS01 Tab Editor.png


Watch the video for a demonstration of the Tab Editor.

Sub-Tab

Now we must create the sub-tab for the child table, so click on the New Record icon and enter the details for the sub-tab.

Note that the Tab Level must be set to 1 because it is a sub-tab (the default level is 0).

Scroll down and click on the Create Fields icon to generate the fields for the sub-tab:

GS01 Create Sub Tab.png

Menu

You must create a Menu entry to use the new window, so click on the button to create a new menu (on the right of the Menu entry in Favourites).

Enter the name, set the Action to Window and use the dropdown in Window to find the window that you created above, and save the record:

GS01 Create Menu.png


Note: the new menu entry will appear in the list of menu items on the left but it will not be active because menu items are loaded when the user logs in for the first time.


Log out and log in again to see the new entry in the Menu tree:

GS01 Menu Created.png


Click on the new menu entry to see the new window with its main tab and the sub-tab below:

GS01 New Window.png

Watch the video and test the new window.

Plug-in

At this point the new model is only available in the development environment, and it is implemented using default mechanisms that contain no Business logic. We need to build a plug-in to hold the new model, together with any bespoke logic that may be required, so that it can be deployed with the base system.

You can watch the video from this point before you follow the summary of the process below.

Plug-in Project

The first step is to create a Plug-in Project.

If you have not already done so, create a sub-directory in your workspace for bespoke plug-ins to keep the codebase clean (in this example, the sub-directory is called zerlina-plugins).

File > New > Other > Plug-in Project > Next >

GS01 New Plug-in Project.png


Note:

  • Use default location box is unchecked so that the plug-in project location can be used instead;
  • Target Platform is the Equinox OSGi framework.


GS01 New Plug-in Project 1.png


Note: the author has selected JavaSE-1.6 to ensure that the plug-in is backwards compatible with older servers.

Answer Yes if Eclipse asks you if you want to use the Plug-in perspective.

Package for the model

Now navigate to the src folder of the new project and create a new package:

Right-click on src > New > Package

GS01 New Java Package.png


Note: the name of the package is org.evenos.model

Model generator

Generate the models with the model generator:

Run > Run Configurations... > model.generator > Run


After a few seconds the Model Class Generator will open:

GS01 Model Generator.png


Note:

  • use the '...' icon in Source Folder to browse to the src folder of the project in the plug-ins sub-directory;
  • Package Name is 'org.evenos.model';
  • Table Name is 'EVE_%' to generate models for both tables;
  • Entity Type is 'U' for User Maintained.

Refresh the project to see the newly generated model classes.

'M' Classes

Create two 'M' classes to hold the business logic for each of the two tables:

Right-click org.evenos.model > New > Class > Name: MEVEMain > Finish

Add two constructors and a generated serial version ID:

package org.evenos.model;

import java.sql.ResultSet;
import java.util.Properties;

public class MEVEMain extends X_EVE_Main{

	/**
	 * 
	 */
	private static final long serialVersionUID = -4652910060540398746L;

	public MEVEMain(Properties ctx, ResultSet rs, String trxName) {
		super(ctx, rs, trxName);
		// TODO Auto-generated constructor stub
	}

	public MEVEMain(Properties ctx, int EVE_Main_ID, String trxName) {
		super(ctx, EVE_Main_ID, trxName);
		// TODO Auto-generated constructor stub
	}
}

Repeat for MEVESub:

package org.evenos.model;

import java.sql.ResultSet;
import java.util.Properties;

public class MEVESub extends X_EVE_Sub{

	/**
	 * 
	 */
	private static final long serialVersionUID = -2076611601274220946L;

	public MEVESub(Properties ctx, int EVE_Sub_ID, String trxName) {
		super(ctx, EVE_Sub_ID, trxName);
		// TODO Auto-generated constructor stub
	}

	public MEVESub(Properties ctx, ResultSet rs, String trxName) {
		super(ctx, rs, trxName);
		// TODO Auto-generated constructor stub
	}
}

To get some idea of what business logic is possible in these classes, look at the 'M' classes in the package 'org.compiere.model' in the project 'org.adempiere.base' which is the core of iDempiere.

Dependencies

The operation of the new plug-in is dependent on two other plug-ins, so use the Add... button to add 'org.adempiere.base' and 'org.adempiere.plugin.utils' to the plug-in manifest:

GS01 Manifest Dependencies.png

Model Factory

The plug-in requires a custom model factory to create the new class model instead of relying on the default model factory.

Create a new package 'org.evenos.factories' with a new class 'MyModelFactory':

package org.evenos.factories;

import java.sql.ResultSet;

import org.adempiere.base.IModelFactory;
import org.compiere.model.PO;
import org.compiere.util.Env;
import org.evenos.model.MEVEMain;
import org.evenos.model.MEVESub;

public class MyModelFactory implements IModelFactory{

	@Override
	public Class<?> getClass(String tableName) {
		// TODO Auto-generated method stub
		if(tableName.equalsIgnoreCase(MEVESub.Table_Name))
			return MEVESub.class;
		
		if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))
			return MEVEMain.class;
		
		return null;
	}

	@Override
	public PO getPO(String tableName, int Record_ID, String trxName) {
		// TODO Auto-generated method stub
		if(tableName.equalsIgnoreCase(MEVESub.Table_Name))
			return new MEVESub(Env.getCtx(), Record_ID, trxName);
		
		if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))
			return new MEVEMain(Env.getCtx(), Record_ID, trxName);
		
		return null;
	}

	@Override
	public PO getPO(String tableName, ResultSet rs, String trxName) {
		// TODO Auto-generated method stub
		if(tableName.equalsIgnoreCase(MEVESub.Table_Name))
			return new MEVESub(Env.getCtx(), rs, trxName);
		
		if(tableName.equalsIgnoreCase(MEVEMain.Table_Name))
			return new MEVEMain(Env.getCtx(), rs, trxName);
		
		return null;
	}
}

Component Definition

The custom model factory must be made known to the OSGi framework so that it will be invoked in preference to the default model factory.

Create a new folder 'OSGI-INF' in the project and then create a Component Definition file:

Right-click OSGI-INF > New > Other... > Plug-in Development > Component Definition > Name: ModelFactory.xml > Finish

Enter the following:

GS01 OSGI-INF 0.png

Note:

  • Name is 'org.evenos.newwindow.model.factory'
  • Class is 'org.evenos.factories.MyModelFactory' (use the Browse button to find it)
  • Click on Add Property to create the 'service.ranking' property with type Integer and value 100 which will ensure that OSGi uses the custom model factory in preference to the default version

Now add IModelFactory to the list of Provided Services on the Services tab of the Component Definition:

Click 'Services' tab > Provided Services Add.. > IModelFactory and save the definition file.

Finally, update the Manifest so that the Component Definition loads every service defined in the OSGI-INF directory by changing the last line as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Newwindow
Bundle-SymbolicName: org.evenos.newwindow
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.adempiere.base;bundle-version="4.1.0",
 org.adempiere.plugin.utils;bundle-version="4.1.0"
Service-Component: OSGI-INF/*

Note: this last step is optional, but the presenter of the video recommends it as good practise.

The video includes a useful discussion on building business logic into the 'M' Classes at this point.

Close-out

The plug-in is now complete, so you can build the product and deploy it for use.