Jump to content

Extending ADempiere

From Wikiversity
Type classification: this is a lesson plan resource.
Type classification: this resource is a course.
Type classification: this is a workshop resource.

Introduction

[edit | edit source]

Extension architecture

[edit | edit source]
  • Callouts
  • Model Validator
  • Java Triggers
  • Processes
  • Views and Reports
  • Forms
  • Print Formats
  • Import File Loader

Common code

[edit | edit source]

Context variables

[edit | edit source]

Context variables are like 'global' variables for the whole Adempiere, or for a specific window or tab.

You can see all context variables in Tools -> Preference -> Context

Context variables starting with # are defined at login level

Every field in a window has his own context variable, you can access it programatically.

Reading context variable AD_Role_ID

int currole_id = Env.getContextAsInt(ctx, "#AD_Role_ID");

Creating object (new record, update record, delete record)

[edit | edit source]

You can get or create objects from the database using the model classes, for example to read an invoice from the database:

MInvoice inv = new MInvoice(getCtx(), invoice_id, get_TrxName());

This is equivalent to issue a 'SELECT * FROM C_Invoice' and load in memory the data.

To create a new record in the database you call the same code with zero as the ID:

MInvoice inv = new MInvoice(getCtx(), 0, get_TrxName());

You then can make changes to columns using the setter methods of the object, i.e.:

inv.setC_Currency_ID(100);

After you make all the changes you save the record in the database calling the method save():

inv.save();

The method save will execute an INSERT if is a new record or an UPDATE if the record already exists. If you want to delete an existing record, you can call the method delete(), i.e.

inv.delete(true);

and after deleting you need to call the method save to execute the corresponding DELETE in the database

Use messages for translations

[edit | edit source]

To show messages within your programs, please use the i18n facility of Adempiere using the message table for this purpose, programatically you simply execute, i.e.:

String dateLabel = Msg.getMsg(Env.getCtx(), "Date");
  • Usage of embedded SQL – recommended to use UpperCase in SQL Keywords (for security sql parser)
    • Specially this keywords: SELECT, FROM, WHERE, ON, AS, INNER, JOIN, LEFT, OUTER, FULL
  • When using joins, security parser also requires that ON clause defining the joining columns must be enclosed in parenthesis
  • use Oracle SQL standard syntax that can be translated to postgres – Don't use postgres specific syntax

If you want to issue a simple query returning just one column of the first record, you can use the DB.getSQLValue functions provided by Adempiere:

String sql = "SELECT COUNT(*) FROM C_Recurring_Run WHERE C_Recurring_ID=?";
int current = DB.getSQLValue(get_TrxName(), sql, getC_Recurring_ID());

To execute a DML operation in the database (INSERT, DELETE or UPDATE) you can use the DB.executeUpdate method, i.e.:

String sql = "UPDATE C_CashLine SET Processed='N' WHERE C_Cash_ID=" + getC_Cash_ID();
int noLine = DB.executeUpdate (sql, get_TrxName());

To read several records from the database (cursor) you can execute

String sql = "SELECT C_PaymentAllocate_ID FROM C_PaymentAllocate WHERE C_Payment_ID = ?";
PreparedStatement pstmt = null;
pstmt = DB.prepareStatement(sql, get_TrxName());
pstmt.setInt(1, payment_ID);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
   payment_allocate_ID = rs.getInt(1);
   /// ... more code
}
rs.close();
pstmt.close();

Commit (not common – used with transactions)

[edit | edit source]

Normally you don't need to execute directly a commit. If in your process you need strict control of the commit, please create a transaction:

Trx trx = Trx.get(getTrxName(ctx), true);
trx.commit()

Callouts

[edit | edit source]

Called after user entered value (for Strings is called every keystroke)

It can be used for direct data validation – but you need to validate again before saving

Better usage for data consequences: i.e. Filling other fields with lookup values, calculating totals You must repeat all calculations in persistence layer

You can have multiple callouts separated by ;

The processing of a callout must be quick (< 1 sec), if slow better create a button and process


Callout classes must extend CalloutEngine and implement the callout methods with 6 parameters (last optional)

  • Context
  • Window No
  • Model Tab
  • Model Field
  • The new value
  • The old value
  • 'Returns' error message

There are also callouts for translation of strings on Import File Loader, these callouts have just one parameter:

  • The value
  • 'Returns' the translated value

Exercise: Create a Callout on the Name of Product Category – the name must be passed to the Description in upper case

Model Classes (lookup)

[edit | edit source]

Persistence

PO (Persistent Object) --> X_<table> classes --> M<shortTable>

Class X_ is looked up with complete Name (and case) of the table

Class M is looked up with table name without prefix (if prefix is <= 2 char), and without underscore symbols “_”

Precedence when looking up for M classes:

  1. Look for model package of the entity type (for non-dictionary tables) – first look for M and then X_
  2. look for model package in:
org.compiere.model
org.compiere.wf
org.compiere.print
org.compiere.impexp
compiere.model
adempiere.model
org.adempiere.model
  1. look for adempiere.model.X_ class
  2. look for compiere.model.X_ class
  3. look for org.compiere.model.X_ class

Exceptions:

AD_Element - M_Element
AD_Registration – M_Registration
AD_Tree – MTree_Base
R_Category – MRequestCategory
GL_Category – MGLCategory
K_Category – MKCategory
C_ValidCombination – MAccount
C_Phase – MProjectTypePhase
C_Task – MProjectTypeTask

If can't find persistence class then it uses PO to save directly to the database.
In Adempiere there is a GenericPO that can be used for programatically save tables with no model class.

X_ classes are generated automatically – don't change them
Usage of GenerateModel:
arguments:

  • Output Directory - C:\srcAdempiere\trunk\base\src\org\compiere\model\
  • Package - org.compiere.model
  • EntityType - 'D'
  • Optional table like - 'U_RoleMenu'

Model Classes (triggers)

[edit | edit source]

Insert/Update triggers: beforeSave and afterSave

Delete triggers: beforeDelete and afterDelete

Model validator

[edit | edit source]

Defined at client level

You can have multiple model validators separated by ;

Events:

  • User Login – you know the user, role, client and organization – i.e. Useful for veto login

Events on table:

  • BEFORE/AFTER
  • NEW/ CHANGE/ DELETE

Events on documents:

  • BEFORE/AFTER
  • PREPARE/ VOID/ CLOSE/ REACTIVATE/ REVERSECORRECT/ REVERSEACCRUAL/ COMPLETE / POST

Model Classes vs Validators

[edit | edit source]
  • Don't customize adempiere model classes – implement triggers for official tables in model validator
  • Use model classes (or model validator if preferred) for your customized tables
  • Don't generate X_ classes for official adempiere tables – use general getter and setter from PO for custom columns

Exercises:

  • Create a ModelValidator that avoid users login twice in Adempiere
  • Create a ModelValidator when Order change the business partner – add the previous bp to the description
  • Create a ModelValidator to forbid GardenAdmin to complete an Invoice
  • Create a ModelValidator to avoid completely posting from invoices

Processes

[edit | edit source]

Called from menu or buttons
prepare method for getting the parameters into variables – Record_ID just work for buttons

doIt method for execution of the process

addLog to keep log of the executions / for auditing purposes and showed at the end of the process

return a message

for errors must throw exceptions

Forms

[edit | edit source]

Custom swing windows – special cases
Not recommended – not supported for webUI clients

  • Extend org.compiere.swing.CPanel
  • Swing code – implement listeners
  • Use javax.swing and org.compiere.swing components

Import File Loader

[edit | edit source]
  • Stage table
  • File definition
  • Import of the file
  • Import process to pass from the stage table to the definite tables

Exercise:

  • Add POReference column to the I_Invoice stage table
  • Change the ImportInvoice process to manage the import of this new column

Other Resource

[edit | edit source]