Sie sind auf Seite 1von 91

qwertyuiopasdfghjklzxcvbnmqwertyui opasdfghjklzxcvbnmqwertyuiopasdfgh jklzxcvbnmqwertyuiopasdfghjklzxcvb Developers Guide nmqwertyuiopasdfghjklzxcvbnmqwer The ADempiere Bazaars Sourcecode Expose tyuiopasdfghjklzxcvbnmqwertyuiopas dfghjklzxcvbnmqwertyuiopasdfghjklzx cvbnmqwertyuiopasdfghjklzxcvbnmq wertyuiopasdfghjklzxcvbnmqwertyuio

pasdfghjklzxcvbnmqwertyuiopasdfghj klzxcvbnmqwertyuiopasdfghjklzxcvbn mqwertyuiopasdfghjklzxcvbnmqwerty uiopasdfghjklzxcvbnmqwertyuiopasdf ghjklzxcvbnmqwertyuiopasdfghjklzxc vbnmqwertyuiopasdfghjklzxcvbnmrty uiopasdfghjklzxcvbnmqwertyuiopasdf ghjklzxcvbnmqwertyuiopasdfghjklzxc vbnmqwertyuiopasdfghjklzxcvbnmqw ertyuiopasdfghjklzxcvbnmqwertyuiop
1/9/2008 Mario Calderon, Norbert Wessel and others (from the Spanish account of Victor Perez, then into German)

Developers Guide

The ADempiere Bazaar

Contents
ADEMPIERE DEVELOPER'S DOCUMENTATION ............................................................................ 4 Class org.compiere.util.GenerateModel................................................................................................ 5 Java-Beans ............................................................................................................................................ 7 Persistency-Engine ................................................................................................................................ 9 Additional methods of PO............................................................................................................... 10 class POInfo .................................................................................................................................... 13 class POInfoColumn ....................................................................................................................... 15 Business Object Classes ...................................................................................................................... 16 Interface DocAction ............................................................................................................................ 19 class MDocType ................................................................................................................................. 21 class X_C_DocType ....................................................................................................................... 22 Documents Workflow ......................................................................................................................... 23 class DocumentEngine .................................................................................................................... 23 Class MWFActivity (Workflow Activity) .......................................................................................... 26 class MWorkflow ............................................................................................................................ 30 Class ModelValidationEngine ............................................................................................................ 31 Class VDocAction............................................................................................................................... 33 Show a window from menu ................................................................................................................ 35 Class APanel ....................................................................................................................................... 37 Class processModalDialog ................................................................................................................. 38 Class MWFprocess ............................................................................................................................. 40 Class Workflowprocessor ................................................................................................................... 41 Class MWFNode ................................................................................................................................. 42 Class processCtl .................................................................................................................................. 43 Invoice-Preview .............................................................................................................................. 45 Values in Table of a invoice previews ............................................................................................ 45 Calling sequence to show the Invoice-Preview .............................................................................. 45 PrintFormat ......................................................................................................................................... 57 class PrintData..................................................................................................................................... 58 Workflow (WF) im AD....................................................................................................................... 60 Workflow-Window ............................................................................................................................. 61 Window Report & Process .................................................................................................................. 64 Parameter analysis at report call ..................................................................................................... 68 Callouts ............................................................................................................................................... 69 Validation of context values ............................................................................................................... 71 Accounting processor .......................................................................................................................... 73 Class Fact ............................................................................................................................................ 76 Class FactLine ................................................................................................................................. 77 DocTypes and DocBaseTypes ............................................................................................................ 78 Application Dictionary: Financial Report ........................................................................................... 79 Interaction of Application Dictionary-Application-Code using Landed Costs as an example ........... 80 Price lists ............................................................................................................................................. 82 Price calculation .............................................................................................................................. 84 Payment Term ................................................................................................................................. 85 Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 2

Developers Guide

The ADempiere Bazaar

Views RV_C_INVOICE, RV_OPENITEM ....................................................................................... 86 Explanation of the warehouse structure in the DB ............................................................................ 89 Attribute Set Instances .................................................................................................................... 91

If you tell the truth you don't have to remember anything.


Mark Twain

It is not enough just to learn and know, but you ought to possess and own it
Aristotle

I hope to free my followers from clinging to styles, patterns, or molds


Bruce Lee, commenting on his Kung Fu

A question may be in the future tense, and so it cannot be answered just yet
Red1, when asked why things arent done

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 3

Developers Guide

The ADempiere Bazaar

ADEMPIERE DEVELOPER'S DOCUMENTATION


This is a documentation of mainly how ADempiere classes are organized and how they interact with each other to yield the functionality for which they were conceived. The order is quite random, but it is intended that each section is as atomic as possible, i.e. each section should not rely too heavily on other sections to be understood. It is by no means a complete documentation, but it could serve as a basis to it. It is our hope that the community uses this document heavily: corrections and enhancements are therefore welcome. The practical purpose is that a person with a basic knowledge of Java and the basic functions of ADempiere should be capable after reading this document of doing developments and customizations on his own, contributing to the improvement of ADempiere. This paper was born after my visit to Ecuador in August 2007, where Victor Perez told me in a 7-day jam session the innards of ADempiere. I added comments which I had written for other aspects of ADempiere. As a result, I wrote a 80+ pages in German. Another contributors were Norbert Wessel and Moritz Weber of Metas Consult, who were so kind to translate the original documentation into English and some remarks from the forum by Karsten Thiemann. Other helpers were XXXXXXX, who set up a Doc Wiki Book, but it was not continued. I sincerely hope that this documentation will become a seed of a proper developers' guide. Mario Calderon San Salvador, December 26th, 2007

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 4

Developers Guide

The ADempiere Bazaar

Class org.compiere.util.GenerateModel
package org.adempiere.util public class GenerateModel The persistence layer of ADempiere consists (mainly) of the PO.java, which is an abstract class and defines methods like load(), save(), delete(), get_ID(), etc. Every persistent x object (a table in the database) has to extend PO.java in order to get all the persistence functionality (in some cases you don't need it but it never hurts.) To ease the expansibility and because of the fact that most needed methods are just setter and getter methods (the X_* classes are simply POJOs) ADempiere offers a class to generate X_* classes that extend the PO.java for all tables (x objects) defined in the application dictionary (AD). This is done with org.compiere.util.GenerateModel () before ADempiere 3.3.0 org.adempiere.util.GenerateModel Now we generate not only an X_* class but also an interface I_* which is implemented by the X_* class. Since ADempiere 3.3.0 That is all what you need to have a full functional (in terms of persistency) persistent x object. The new window tutorial in the wiki gives an example. It creates a table to store material information - it is just an example (from my company). You don't need to create a M_* class for it if you just want to store/load the data into an ADempiere window. For that the generated X_XX_Material.java is sufficient. In the given example the MMaterial.java adds some functionality in order to get a MMaterial xobject with the given materialno and colorno. This is a quite common functionality in M_* classes. Give me the x object / all x objects with a given set of attribute values from the database - but it is only an example and you don't need to implement this kind of functionality for your x object. In GenerateModel.java: If someone inputs no or a wrong path, errors will be caused. It is best practice to change GenerateModel.java 1) Change the default folder to your own 2) optional change the package name 3) optional Add entity types like 'D' (Dictionary) to line 97 Run GenerateModel without parameters

method main() generates java beans (X_xxxxxx.java-files like e.g.e.g. X_C_Invoice.java). The method main() reads the tabell AD_Table and generates amongst others get- and setmethods for every column of AD-tables. It is possible to generate single beans See in the text down below. Page 5

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar

In the sources of Adempiere these files can be found at org.compiere.model . The compiled GenerateMdel.class-file is saved at .../adempiere_trunk/base/build/org/compiere/util With parameters it is possible to configure the output: select the method main() in Eclipse, click the right mouse button and click run in the context menu To input parameters, they must be defined in run Parameter 0 : Folder Where to copy the file. Default when no parameter set: "C:\\Adempiere\\adempiereall\\extend\\src\\adempiere\\model\\" In svn trunk this is in /adempiere_trunk/extend/src/compiere/model// Parameter 1: Package,that the java bean contains to Default when no parameter set: "compiere.model" Parameter2: entity type (User-Defined, Adempiere, Dictionary, etc). In the file there is just User and Application intended. For others like Adempiere, it should be entered there. Remember that just User-defined will not be deleted in every new version Default when no parameter set: "'U','A'" Parameter 3: tabelle Relating to which table this java bean will be generated Default when no parameter set: % (all tables) Also the code can be changed that way, that the parameters have the wished configuration. Every time the application dictionary tables has been changed, the X_files must be regenerated to acces the field from source code.

Since V. 3.3.0 /Adempiere/adempiere_trunk/base/src/org/adempiere/util/GenerateModelJPA.java is designed to do this. This class generates files without X_-prefix , e.g. C_InvoiceLine.java. They must be renamed before deployment.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 6

Developers Guide

The ADempiere Bazaar

Java-Beans
Characteristics of beans They are POJOs Every business object has a java bean They are the connection between source code and the data base data. They help the developer avoid to access data directly or keeping infomation like Table IDs or column names hard-coded. Example bp.getM_PriceList_ID(). The object BusinessPartner gives back the id of the price list. They are placed in the package org.compiere.model, where the java bean classes and the business logic classes are placed Declaration of the java bean class. For example: public class X_C_Invoice extends PO Some methods are implemented in the class PO (Persistence Object in org.compiere.model.PO) For example all inherited constructors call the constructor of the java bean class PO ( super (ctx, rs, trxName) or super (ctx, C_Invoice_ID, trxName) ). static final Property Table_ID table contains the ID of the table example: public static final int Table_ID=MTable.getTable_ID(Table_Name); protected static Property Model example: protected static KeyNamePair Model = new KeyNamePair(Table_ID, Table_Name); static final Property accessLevel Are values which have been defined at table creation (Client, Organisation, Client+Organisation, etc). example: protected BigDecimal accessLevel = BigDecimal.ValueOf(1); AD_ORGTRX_ID_AD_Reference_ID ID in dictionary. methods initPO will be called by the last PO-constructor of the call chain ( PO (Properties ctx, int ID, String trxName, ResultSet rs) ) . toString (e.g.e.g.gives back for example the class C_Invoice: X_C_Invoice) It is used in compare() or log.info(toString() get- and set-metods of all columns of the table. Exception: columns processing, processed, Created, CreatedBy, etc. that are created in the method load() of the class PO with the call of loadDefaults()/SetStandardDefaults(). getDocStatus() setDocStatus() Misc All base classes and beans are part of the same package. This makes it possible to create an object by calling the constructor. Date is handled as Timestamp . Boolean values are strings with length 1 (Y=True, N=False). When querying booleans, no get method is called. Use methods like isApproved(). There are more beans that can be found in different packages. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 7

Developers Guide

The ADempiere Bazaar

They will be partly explained in this document, for example MWFActivity, MWFNode, MWFprocess, MDocType, MWorkflow. There are java beans which are not related to a business object: public class X_AD_WF_Activity extends PO This class is a helper class for MWFActivity public class MWFActivity extends X_AD_WF_Activity implements Runnable Beans and PO together realize persistency in ADempiere: Beans keep data PO supplies mechanism for persistency

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 8

Developers Guide

The ADempiere Bazaar

Persistency-Engine
org.compiere.model.PO Realizes class persistency of ADempiere. PO has methods for DB transfer and also calls triggers and model validators. public abstract class PO implements Serializable, Comparator, Evaluatee; Serializable For streaming out objects. Comparator methods compare() and equals() are implemented by PO. Evaluate Implements the method get_ValueAsString(), which is also called by org.compiere.util.Evaluator. evaluateLogicTuple() . The tupels @xxxx@ defined in AD, are evaluated. Class hierarchy: an Adempiere-class like MInvoice inherits from X_C_Invoice, who itself inherits from PO : public class MInvoice extends X_C_Invoice implements DocAction public class X_C_Invoice extends PO

Important constructors: the root-constructor is called at last: public PO (Properties ctx, int ID, String trxName, ResultSet rs) To create a new instance: if the parameter ID ist set with value 0. If the transaction is set as null, a new one will be created. org.compiere.util.Trx controlls transactions with static methods When new transactions are created, Trx sets a random name. Trx.createTrxName() Create a new transaction, e.g. Trx.createTrxName("Cost") Name of the current transaction will be got Trx.get(trxName, True) A new transaction will be created if transaction trxName does not exist and the second parameter is True. Trx.get(trxName, True) In Trx.get a new will be created: { : retValue = new Trx (trxName) : } Setting the transaction name assures that all DB savings with equal names are handled as one transaction in commit and rollback contextes. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 9

Developers Guide The root-constructor calls: { : load (int ID, String trxName) : }

The ADempiere Bazaar

If ID and transaction are known ID > 0 The columns are being retrieved via SQL and instance is filled with values. ID <= 0 Creation of new object. Private class m_createNew gets the value true, so that object knows that it is a new creation. Columns processing, processed, Created, CreatedBy, etc. are created with calling loadDefaults(). loadComplete (boolean success) Can be defined by the subclasses if needed, but it is currently not used anywhere. load(ResultSet rs); load(ResultSet rs) also calls in the end load (int ID, String trxName). For this a result set is used to create an object. The current position of the result set is taken; no navigation occurs through the result set. Constructor PO (Properties ctx, int ID, String trxName) MBPartner bp = new MBPartner (line.getCtx(), line.getC_BPartner_ID(), line.get_TrxName()); Constructor PO (Properties ctx, ResultSet rs, String trxName)

Additional methods of PO
public final Object get_Value (String columnName) It checks whether column is active If active, get_Value (int index) is called, which returns m_newValues[index]. See Properties. set_ValueNoCheck (String ColumnName, Object Value) The column ColumnName gets a value without checking. Value can be cut off. That is the reason why sometimes values are typed in into the program and the programm saves them shortened. They are shorted to the length defined in Adempiere's AD. The following methods are explained only here and not in the subclasses which inherit from PO. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 10

Developers Guide

The ADempiere Bazaar

delete() An object is deleted. Under certain conditions, events are validated with ModelValidationEngine. delete_Tree() ID-Trees are deleted save() Thr saving of subclasses' instances done here. Some checks are done: new object, organisation. In save() other methods are called: beforeSave() Is often implemented by business classes like MInvoice, MProduct and so on. saveNew() Saving of a new object saveUpdate() Saving of an existing object. Change tracking A session variable is created. MSession session = MSession.get (p_ctx, false) If a value change occurs, the table MChangeLog saves the state before and after saving: MChangeLog cLog = session.changeLog In order to achieve this, configuration must be done in Adempiere A table can be defined for logging there. If this is done, Adempiere shows the change log in the window, where the table is displayed, by double-clicking on the bottom rigth of the window (click right mouse button on the bottom right corner of the table window). The following is shown: Tablename When and by whom the record was created When and by whom the record was modified change log saveNew() and saveUpdate() call saveFinish() at the end, which calls afterSave(). Methods beforeSave() and afterSave() are often implemented by business classes like MInvoice, MProduct etc. . Properties of PO (among others) POInfo p_info (Column info: Table-ID, Table Name, Access Level, etc). Information about columns is provided by p_info. This property is being taken from the root-constructor with p_info = initPO(ctx); Subclasses implement initPO(). See class POInfo. Column Information: A call of get_Value("Columnname") causes a call of get_Value (int index); which returns m_newValues[index]. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 11

Developers Guide

The ADempiere Bazaar

(or m_oldValues[index], when m_newValues==null) protected transient CLogger log = CLogger.getCLogger (getClass()) To show the subclass-output of the system console private static Clogger s_log = CLogger.getCLogger (PO.class); To show the PO-output in the system console private Doc m_doc private Object[] m_IDs = new Object[] {I_ZERO} // Ids of the data sets private Object[] m_oldValues = null; // old values private Object[] m_newValues = null; // new values private Mattachment m_attachment = null; etc.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 12

Developers Guide

The ADempiere Bazaar

class POInfo
package org.compiere.model public class POInfo implements Serializable The class provides the access to POInfoColumn-instances Contains information about columns of the business object is serializeable (for exporting functionality) Properties (incomplete list) m_AD_Table_ID m_TableName m_AccessLevel POInfoColumn[] m_columns private Properties m_ctx = null Methods (incomplete list) Refer mostly to properties of m_column using the index. The Constructor POInfo (Properties ctx, int AD_Table_ID, boolean baseLanguageOnly) calls loadInfo(), who in turn uses a SQL-Statement which comprises the tables AD_Table t AD_Column c AD_Val_Rule vr and AD_Element e (Parameter for SQL:m_AD_Table_ID), Creates for each column an instance of POInfoColumn and fills the instance with the SQL data t.TableName c.ColumnName c.AD_Reference_ID c.IsMandatory c.IsUpdateable c.DefaultValue e.Name,e.Description c.AD_Column_ID c.IsKey c.IsParent c.AD_Reference_Value_ID vr.Code c.FieldLength c.ValueMin c.ValueMax c.IsTranslated t.AccessLevel c.ColumnSQL Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 13

Developers Guide

The ADempiere Bazaar c.IsEncrypted Information of all columns are copied to m_columns for Translations table AD_Element_TRL is used and the language is selected public static POInfo getPOInfo (Properties ctx, int AD_Table_ID) Calls the constructor. getPOInfo() itself is called in initPO() , within the Java Beans for the related table ID. initPO() is called inside the PO-Constructor. getColumnName (int index) isColumnMandatory (int index) String getColumnDescription (int index) getColumnCount() Class getColumnClass (int index)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 14

Developers Guide

The ADempiere Bazaar

class POInfoColumn
package org.compiere.model public class POInfoColumn implements Serializable Contains Information about a column of the business Object Properties (all public) AD_Column_ID ColumnName ColumnSQL DisplayType ColumnClass IsMandatory DefaultLogic IsUpdateable ColumnLabel ColumnDescription AD_Reference_Value_ID ValidationCode FieldLength ValueMin ValueMax ValueMin_BD ValueMax_BD methods IsKey IsParent IsTranslated isUpdateable toString IsEncrypted Relationship between POInfoColumn, PO and beans (X_-classs) POInfoColumn instances contain information about properties the business object (=columns) Beans contain data PO manages data fetching and data saving

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 15

Developers Guide

The ADempiere Bazaar

Business Object Classes


package org.compiere.model There are two kind of business objects Workflow-Business-Objects They are called inside a workflow. They must fulfill the requirements of the workflow additionaly to their BO responsibilities. e.g: MInvoice, MOrder class definition public class MInvoice extends X_C_Invoice implements DocAction With DocAction the class implements the methods that are needed to realize a workflow. These methods are triggered by buttons, which can be defined at the window Table&Columns in the Application Dictionary of Adempiere. Depending of the state and the next DocAction, the corresponding method is called. For more information see the Workflow Chapter. The following methods query states and set actions. prepareIt() Events are validated with ModelValidationEngine. Class ModelValidationEngine can be used, to realize custom business logic. Here listened events are caught and executed, referenced document states and document types are defined and other methods are called. An custom class can be defined in the client window, Field validation class. An example for an own validation class can be found in Libero. completeIt() Events are validated amonst others with ModelValidationEngine approveIt() etc. Document workflow classses define the property private String m_processMsg, which shows messages as "PeriodClosed" in the bottom left of the coresponding window. Methods that implment the interaction of actual business logic, for example in Minvoice validatePaySchedule() testAllocation() getOpenAmt() etc.

Master data of business objects, for example in MProduct public class MProduct extends X_M_Product no implement-part Methods that realize the interaction of actual business logic , for example in MProduct isProductStocked() isOneAssetPerUOM() getAttributeSet() Page 16

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar etc.

properties depend on the purpose of the class . All BOs implement trigger methods beforeSave() Actions before Data is saved. afterSave() Actions after data is saved. beforeDelete() Actions before data is deleted afterDelete() Actions after data is deleted Business-classes implement different constructors depending on their use. Examples: Standard Constructor MInvoiceLine (Properties ctx, int C_InvoiceLine_ID, String trxName) MInvoice to = new MInvoice (from.getCtx(), 0, null); public MProduct (X_I_Product impP) Constructor of the MProduct class for the product import. Properties of an instantiated product are set with the value of the object impP . The behavior of the import can be influenced with the class ImportProduct and X_I_Product or with ValidateModel.

Business classes often implement static-method get(), that returns an array of objects of its own class. Example: public static MProduct[] get(Properties ctx, String whereClause, String trxName) Business classes implement a property to log events in the system console private static Clogger s_log = CLogger.getCLogger (Mproduct.class); Business classes implement an object cache private static CCache<Integer,MInvoice> s_cache= new CCache<Integer,MInvoice>("C_Invoice", 20, 2); // 2 minutes Thr number of objects in cache (here 20) and residence duration (here 2 minutes) are different for each object. org.compiere.util.CacheMgt manages the Cache via the Application Server. Business logik Functionality is explained here based on Minvoice.prepareIt(). Steps: Model Validation (if there is one) See chapter on ModelValidation. Gets an instance of DocumentType on basis of "C_DocTypeTarget_ID" Checks whether a period is open based on the DocBaseTyp Page 17

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar

line validation Checking of cashbooks Doctype checking or setting BOM expanding Tax calculation Landed Costs per line First all costs are added and then distributed to the rows In a row all costs are allocated to the product MinvoiceLine allocateLandedCosts() calls MinvoiceLine getBase() Here is calculation of LANDEDCOSTDISTRIBUTION_Costs not implemented Validation Set Docaction

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 18

Developers Guide

The ADempiere Bazaar

Interface DocAction
package org.compiere.process public interface DocAction

WF business objects implement methods defined inDocAction, like in public class MInvoice extends X_C_Invoice implements DocAction. A BO that implements DocAction is called a Document. The final shape depends on the object approveIt() Sets property isApproved to value true closeIt() There is no action possible after this invalidateIt() prepareIt() Logic before execution processIt() Exceution of the business logic reActivateIt() after calling closeIt() document can be reactivated. I depends on the object: an order can be reactivated, but not an invoice rejectIt() reverseAccrualIt() reverseCorrectIt() Generates the entries in accounting. In order to accomplish this, a copy of the original document is taken. unlockIt() A method lockIt() does not exist voidIt() Defines static final Properties (String constants) the actions and states ACTION_Complete ACTION_Close STATUS_Drafted STATUS_Completed etc. Miscellaneous methods getApprovalAmt() getCtx() ID of record getDocAction() getDocStatus() Return sthe state defined as static final Property getDocumentInfo() Name of document type and document number. getDocumentNo() getDoc_User_ID() Page 19

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide Controls sequence e.g. Selling- Purchase- Payment-No. getprocessMsg () Returns the value of the property m_processMsg get_TrxName() save() setDocStatus () Set a state that is defined as static final Property and many more

The ADempiere Bazaar

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 20

Developers Guide

The ADempiere Bazaar

class MDocType
package org.compiere.model public class MDocType extends X_C_DocType

Representss a document typ Is instantiated by BOs with dcument types (in methods like prepare() or getDocumentInfo() ) like in MCash MInOut MInventory MInvoice MJournal MMovement MOrder MPayment MPeriod MRequisition etc. Has few methods and no public properties methods getOfDocBaseType() returns the base document static public MDocType get (Properties ctx, int C_DocType_ID) returns an instance of MDocType from the cache and instanciates an object isOffer() Using properties of X_C_DocType it checks whether the BO is a subtype of Offer. Abstract: DOCSUBTYPESO_Proposal.equals(getDocSubTypeSO()) isProposal() and isQuotation() behave similar to isOffer().

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 21

Developers Guide

The ADempiere Bazaar

class X_C_DocType
Seems also to be generated by GenerateModel . package org.compiere.model public class X_C_DocType extends PO Properties public static final int C_DOCTYPEINVOICE_ID_AD_Reference_ID=170; ID_AD_Reference_ID public static final String COLUMNNAME_AD_PrintFormat_ID = "AD_PrintFormat_ID" public static final String COLUMNNAME_C_DocType_ID = "C_DocType_ID"; public static final String COLUMNNAME_C_DocTypeInvoice_ID = "C_DocTypeInvoice_ID" public static final String COLUMNNAME_C_DocTypeShipment_ID = "C_DocTypeShipment_ID" public static final String COLUMNNAME_IsDefault = "IsDefault" public static final String DOCSUBTYPESO_Proposal = "ON" public static final int DOCBASETYPE_AD_Reference_ID=183; /** AP Credit Memo = APC */ public static final String DOCBASETYPE_APCreditMemo = "APC"; ** AP Invoice = API */ public static final String DOCBASETYPE_APInvoice = "API"; /** AP Payment = APP */ usw. fr Bank Statement (CMB), GL Document(GLD), Material Movement (MMM), GL Journal (GLJ) etc. methods many getter methods call PO-methods like e.g. getDocSubTypeSO(), which calls get_Value("DocSubTypeSO") in PO getName(), which calls get_Value("Name") in PO also setter methods

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 22

Developers Guide

The ADempiere Bazaar

Documents Workflow

class DocumentEngine
Package: org.compiere.process public class DocumentEngine implements DocAction Caveat: the business object m_document is called document here, because it implements methods of DocAction. DocumentEngine identifies the next action, checks (using the BO state) whether the action is valid and excecutes this action of the business object if validation succeeds. Object m_document changes from one state to another during the excecution of the action, which is done by the DocumentEngine. A state machine with actions and states is modeled by this behaviour. DocumentEngine: on one hand it implements methods of DocAction, which means it executes prepareIt(), processIt(), completeIt() etc. and on the other hand it is instantiated so that a BO changes its state by calling a method of the same name that is implemented in the BO: processIt() of BO executes processIt() of DocumentEngine, that is followed by a call of a method like complete() of the BO.

Properties DocAction m_document // A business object casted to DocAction DocumentEngine implements also the interface DocAction and has a Property DocAction String m_status (Default: STATUS_Drafted, defined in DocAction) String m_message String m_action get and set methods setDocStatus(String ignored) does nothing isDrafted() return STATUS_Drafted.equals(m_status); // STATUS_Drafted, defined in DocAction isInvalid() return STATUS_Invalid.equals(m_status); isInProgress() return STATUS_InProgress.equals(m_status); isApproved()return STATUS_Approved.equals(m_status) etc... Methods The methods process business object (document) Constructor Page 23

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar DocumentEngine (DocAction po, String docStatus) property m_document is set. processIt() completeIt() isValidAction (String action) Check with public String[] getActionOptions() whether the action is valid with of the current state of the BO. Example: When the current state=STATUS_Drafted, then the following actions are possible: {ACTION_Prepare, ACTION_Invalidate, ACTION_Complete, ACTION_Unlock, ACTION_Void} (These actions are defined in DocAction). After that the the current state of the BO defines the potential aktions.

The call processIt() triggers the next call of a method within a BO. Typical use of DocumentEngine e.g. in MInvoice processIt(String processAction): DocumentEngine engine = new DocumentEngine (this, getDocStatus()); An instance of DocumentEngine is created for the current BO with its current state. return engine.processIt(processAction, getDocAction()); processAction is expected action of the WF and getDocAction() action desired by the user. DocumentEngine: processIt(processAction, docAction) // own logic Validations are performed, depending on whether WF-Action ist valid, If WF-Action ist not valid, whether the user action is valid, m_action is set. See isValidAction(). After that processIt( m_action) is called. DocumentEngine: processIt(String action) // Implementation of DocAction Depending on m_Action the related (custom) method is called, in which the state is changed. th method of the business objects with the same name is called: if (ACTION_Unlock.equals(m_action)) return unlockIt(); calls m_document.approveIt() if (ACTION_Invalidate.equals(m_action)) return invalidateIt(); Sets m_document.setDocStatus(STATUS_Invalid) if (ACTION_Complete.equals(m_action) ....) completeIt(), which calls m_document.completeIt() of BO. etc. The new state of the BO is set after the execution of this methods. Some DocAction methods like completeIt() of the DocumentEngine check the BO method for validity of the action isValidAction (String action). A special action is ACTION_Complete, because depending on the state, it not only does call completeIt(), but also m_document.save() and postIt().

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 24

Developers Guide

The ADempiere Bazaar

So it might happen that an action will be checked twice for correctness, if the DocumentEngine is used.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 25

Developers Guide

The ADempiere Bazaar

Class MWFActivity (Workflow Activity)


Using the word document in this context means a business object that implements a DocAction interface. Package: org.compiere.wf public class MWFActivity extends X_AD_WF_Activity implements Runnable public class X_AD_WF_Activity extends PO It does not implement the DocAction interface

Definition public class MWFActivity extends X_AD_WF_Activity implements Runnable Inherits from X_AD_WF_Activity X_AD_WF_Activity is generated by GenerateModel and inherits from PO Properties of MWFActivity private m_po // Reference to BO, which executes this activity Extracted with getPO (Trx trx) m_docStatus gets it value during run() or performWork() at DocActions: doc = (DocAction) m_po; // business object instance is taken success = doc.processIt (m_node.getDocAction());// action is processed setTextMsg(doc.getSummary()); processMsg = doc.getprocessMsg(); m_docStatus = doc.getDocStatus(); private Trx m_trx private MWFNode m_node // Node to which action is referenced to private StateEngine m_state = null; private MWFprocess m_process = null; private DocAction m_postImmediate = null; Methods Constructors MWFActivity (MWFprocess process, int AD_WF_Node_ID) Here take place the calls of the implemented methods of the interface X_AD_WF_Activity. There are more constructors constructor is called in Workflowprocessor for servers WFActivity seem to be for clients VDocAction for grids WebInfo other classes

getPO (Trx trx) Gets a reference to a business object Here interact MWFActivity with MTable and PO. Page 26

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar getAD_Table_ID() from X_AD_WF_Activity returns the ID of the object in the table AD_Table. For this X_AD_WF_Activity calls the method get_Value ("AD_Table_ID") of PO, which calculates the value. With the ID, the table of the business object can be obtained getRecord_ID() returns the ID of the business object To do this X_AD_WF_Activity calls the method get_Value("Record_ID") of PO , which calculates the value. After this the reference to PO is queried. run() Is called in FormFrame class, startBatch(), which is in turn called by class VSetup, method actionPerformed() after any kind of events in grids, editors and so on. Calls performWork() It is called in MWFprocess.startNext(), when a button was triggered. See beneath. performWork() Calls success = doc.processIt (m_node.getDocAction()) getActiveInfo (Properties ctx, int AD_Table_ID, int Record_ID) Prepares content and and actions for combo boxes. public void setWFState (String WFState) See the following example. example MinOut.processIt() Abstract: The workflow processor, which was activated by the server, looks for all activities that needs to be processed. Each activitythat is checked for validity and handed over to the MWF process, which considers all activities of a process and triggers the next valid for execution. The next valid activity calls finally processIt(). In DocumentEngine it was already explained what happens afterwards ( processIt() of the BO calls the constructor of DocumentEngine and then documentEgine.processIt() etc.)

The following call chain is executed: A) AdempiereServer: public void run() in a loop doWork() is called after a certain time. Workflowprocessor: protected void doWork() calls Workflowprocessor.wakeup() Workflowprocessor: private void wakeup() All interrupted, not processed activities of workflow nodes with inactive actions are identified via a SQL statement. Tables: AD_WORFLOW->AD_WF_NODE-> AD_WF_ACTIVITY As a workflow is triggered by a process, the activity can be traced Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 27

Developers Guide

The ADempiere Bazaar to a node, the node can be traced to a workflow and the workflow to a process. For each activity activity.setWFState (String WFState) MWFActivity: public void setWFState (String WFState) it checks whether the new state WFState is valid. if yes, the process for Ctx is taken and checkActivities(String trxName) is called MWFprocess: public void.checkActivities(String trxName) All activities of the process, which the current activity belongs to, are scanned: MWFprocess.getActivities(), which contains SELECT * FROM AD_WF_Activity WHERE AD_WF_process_ID=? . The first activity after one that is marked as completed , is given as parameter to MWFprocess.startNext(). With all other activities the state is managed MWFprocess: private boolean.startNext() Set last Activity to processed and call save() next Activity is taken processing the logical operators (AND / XOR) Check whether Activity t is next in chain. Start the Activity using a thread new Thread(new WFActivity(....) ).start(); public synchronized void Thread( ).start(); Comment in code: it calls the run() Method of this thread MWFActivity: public void run() Calls performWork() Parameter: m_trx Comment in code: Feedback to process via setWFState -> checkActivities MWFActivity: private boolean performWork() Fetched from property m_node, which gets the action: String action = m_node.getAction() Possible actions: Document Action, Report, process, Email, Set variable, User Choice. Actions like Task, Sub Workflow, User Workbench, User Form and User Windows are offered, but not implemented. Is the action about to be excecuted equal Action, then calls doc.processIt(DocAction of node) the DocAction for the implementing BO. Parameter is m_node.getDocAction(). Reminder: Actions are Report, process, Document Action etc.; whereas DocActions are prepare, complete and so on.

Sometimes a post immediate is prepared. Page 28

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide Other calls of setWFState (String WFState):

The ADempiere Bazaar

MWFActivity: public void run() calls MWFActivity.setWFState (String WFState) call after that performWork() MWFprocess: setWFState (String WFState) Sets process state and refreshes all actions calls MWFActivity.setWFState (String WFState) seems to be made for Status=closed i do not track this(Comment of the author)

InOutGenerate.completeShipment()

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 29

Developers Guide

The ADempiere Bazaar

class MWorkflow
Package: org.compiere.wf public class MWorkflow extends X_AD_Workflow public class X_AD_Workflow extends PO See explaination in class processModalDialog, where MWorkflow is instantiated. Properties m_nodes: Array of MWFNode-elements private static Ccache<String,MWorkflow[]> s_cacheDocValue

methods loadNodes() Depending on Workflow_ID all nodes of table AD_WF_Node are read into m_nodes . public MWFNode[] getNodes() nodes saved in m_nodes are returned as MWFNode[] public MWFNode[] getNextNodes (int AD_WF_Node_ID, int AD_Client_ID) Identify the nodes about to be executed public MWFprocess start (processInfo pi) Starts a workflow An instance of MWFprocess is created: retValue = new MWFprocess (this, pi) is saved startWork() of this instance is executed. Here a validation takes place whether the workflow can be started with its first node. The first node's activity is identified and executed. See here also the context of the description of a call of actionButton() in class processModalDialog. public int getPrevious (int AD_WF_Node_ID, int AD_Client_ID) Take the previous node from m_nodes. aftterSave() All nodes are saved under some circunstances. some get-methods

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 30

Developers Guide

The ADempiere Bazaar

Class ModelValidationEngine
Package org.compiere.model public class ModelValidationEngine

It is mostly used in calls inside of business classes (in the methods prepareIt(), completIt(), closeIt() and so on) as following: ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_ CLOSE); method get() yields (and sometimes creates) an instance of ModelValidationEngine The result of get(). fireDocValidate (a String) is assigned to the property m_processMsg of the business-class . has a property s_engine, which contains an instance of its own class (!) It therefore seems that there is just one instance of ModelValidationEngine In the constructor of ModelValidationEngine one ModelValidator is instantiated: ModelValidator validator = (ModelValidator)clazz.newInstance(); It seems to be a problem here, because an interface is instantiated here, but interfaces are implemented and not instantiated. Solution: In client window a Client Validator Class can be set, which includes a client validation. If is not there, the validation is ignored Example of a validator-class: compiere.model. ModelValidator, at /extend. public class MyValidator implements ModelValidator. Afterwards, initialize() in Validator is called. fireDocValidate() The method docValidate() is called for every validator.

interface ModelValidator Package org.compiere.model public interface ModelValidator ModelValidator is an interface, which can be implemented by the developer. If the class is registered in Adempiere, on every change of a record or document a specific method can be called. Inside this method developers can programm their own actions as e.g. booking to another account or change booking logic If accounting rules are changed in Adempiere, RUN_setup must be rerun, so that the application server can see the changes. ModelValidator is used to program external logic to adempiere core. Example of a ModelValidators (by Carlos Ruiz): http://adempiere.svn.sourceforge.net/viewvc/adempiere/trunk/extend/src/compiere/model/MyV alidator.java?view=markup

is used inside the constructor ModelValidationEngine Page 31

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar

{... Class clazz = Class.forName(className); ModelValidator validator = (ModelValidator)clazz.newInstance(); initialize(validator, clients[i]); ...} defines a constant like public static final int TYPE_BEFORE_NEW = 1 They are used as parameters of fireDocValidate calls. declares methods like public String docValidate (PO po, int timing) public void initialize (ModelValidationEngine engine, MClient client) public String modelChange (PO po, int type) throws Exception used to implement custom validations. Example of a validation-class: compiere.model. ModelValidator, under /extend.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 32

Developers Guide

The ADempiere Bazaar

Class VDocAction
package org.compiere.grid.ed class VDocAction extends Cdialog implements ActionListener Shows valid operations of document actions depending on the context. If e.g. the complete button in the order window is triggered, a form called VDocAction appears, where the DocAction can be selected. After pressing OK the selected DocAction of the current BO is called, which causes a state change of the BO.

Controlls process window Properties GridTab m_mTab m_AD_Table_ID Is initialized in the constructor with Env.getContextAsInt() Is used in dynInit() as parameter at DocumentEngine.gertValidActions(). private boolean m_OKpressed = false; private boolean m_batch = false; Graphical elements as panels, combo boxes, scroll panes, text areas, etc. Constructor creates a dialog with Panel BorderLayout ComboBox TextArea JButton etc. Methods Constructor VDocAction (int WindowNo, GridTab mTab, VButton button, int Record_ID) Ruft jbInit() and dynInit(Record_ID)auf.. jbInit() Is called by the constructor. Initializes window. The property ActionLabel gets the correct value. dynInit()Is called by the constructor. Identifies the valid actions based on the state of the business object. (documents) The workflow state is detected: wfStatus=MWFActivity.getActiveInfo() Calls DocumentEngine.getValidActions(), which realizes the following: First the actions are identified depending on the DocState e.g. in status_NotApproved --> Action_Prepare and Action_Void Then depending on table and state, additional Actions are added.The tables are: Order, MinOut (Shipment), Invoice, Payment, GL-Journal,

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 33

Developers Guide

The ADempiere Bazaar Allocation, Bank Statement, Inventory Movement, Physical Inventory. At least the combo box with shortforms is created: CO, CL, DR, etc. actionPerformed() does some query save() Causes via a complicated mechanism that this command triggers a saving on the database: m_mTab.setValue("DocAction", s_Value[index])

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 34

Developers Guide

The ADempiere Bazaar

Show a window from menu


org.compiere.apps AMenuStartItem.run() The column Action is extracted from table AD_Menu (or AD_WF_Node, if it is not a menu). The associated column is read depending on Action: AD_WINDOW_ID, AD_process_ID, AD_WORKFLOW_ID, AD_FORM_ID. If it is a window, the call is startWindow(0, Window-No.). Window-No. is passed on through the chain of calls. org.compiere.apps AMenuStartItem.startWindow(int AD_Workbench_ID, int AD_Window_ID) Is used both for workbench and normal windows. If AD_Workbench_ID==0, then AD_Window_ID is evaluated. A method is called by the instance called frame of the class AWindow: frame.initWindow(AD_Window_ID, null) The third parameter is an optional query, which is needed for the final window. Here the third parameter is null. org.compiere.apps AWindow.initWindow (int AD_Window_ID, MQuery query) In the Awindow constructor (last method) the class variable m_APanel was initialized. In initWindow() the method m_APanel.initPanel (0, AD_Window_ID, query) is called.

Side-explanation begin How can a window be called from an arbitrary place in code. import org.compiere.model.*; // because of MQuery import org.compiere.apps.*; // because of AWindow : : MQuery my_query = new MQuery ("C_BPartner"); // e.g.. C_BPartner my_query.setRecordCount(1); // if only one BP is to be shown my_query.addRestriction("C_BPartner_ID", "=", 1000598); // BP ID AWindow frame = new AWindow(); boolean OK = false; OK = frame.initWindow(123, my_query); // 123=BP window frame.validate(); AEnv.showCenterScreen(frame); When table, restriction and window nr. do not match, a window with a new record will be shown. Problem: MQuery is a class.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 35

Developers Guide
Side-explanation end

The ADempiere Bazaar

org.compiere.apps APanel.initPanel(int AD_Workbench_ID, int AD_Window_ID, MQuery query) Here it can be discovered that the workbench alternative was not implemented. So this is just one window. Tabs will be generated, if there are any. If there is query in the tab, it will also be defined there: query = initialQuery (query, gTab) MQuery APanel.initialQuery (MQuery query, GridTab mTab) If MQuery exists, is active and has <10 objects, it follows no other query: if (query != null && query.isActive() && query.getRecordCount() < 10) return query; Otherwise it is a so called volume table and a filter dialog appears. Find find = new Find (Env.getFrame(this), m_curWindowNo, mTab.getName(), mTab.getAD_Table_ID(), mTab.getTableName(), where.toString(), findFields, 10); It takes care of the dialog of volume tables.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 36

Developers Guide

The ADempiere Bazaar

Class APanel
package org.compiere.apps public final class APanel extends CPanel implements DataStatusListener, ChangeListener, ActionListener, ASyncprocess

actionPerformed() Command dispatcher depending on the triggered icon in the window (Save, Print, Attachment, Forward, Backward, etc.) Actions are processed in private methods. Some use processCtl: process() or m_curTab.dataSave(manualCmd) with m_curTab as a grid. actionButton() Creates an instance of VDocAction: VDocAction vda Shows the dialog with vda.setVisible(true). In the dialog the action can be selected (Complete, Void, etc.). calls processModalDialog dialog = new processModalDialog() Shows the last called window via aenv.showCenterWindow(Env.getWindow(m_curWindowNo), dialog)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 37

Developers Guide

The ADempiere Bazaar

Class processModalDialog
package org.compiere.apps public class processModalDialog extends Cdialog implements ActionListener

Abstract: processCtl manages different kinds of processes and causes if needed the instantiation of a workflow. The WF instanciates a MWFprocess, which identifies WF, the node and its activitiy. Finally, the activity is executed with processIt(). It is discussed here mainly because of the call of actionButton():

actionPerformed(actionEvent e) actionPerformed(actionEvent e) is triggered by events from graphical components. Calls processCtl: process() processCtl: process(m_ASyncprocess, m_WindowNo, parameterPanel, m_pi, null) processCtl: public static method process() for synchronized or unsynchronized processes m_pi is of the class processInfo Depending on m_pi.AD_process_ID and m_p.Record_ID, an instance of MPInstance is acquired. In the static method a processCtl is instantiated: processCtl worker = new processCtl(parent, WindowNo, pi, trx); With unsynchronized processes: worker.start() -> starts a new thread start() calls new Thread(this).start(). finally processCtl.run()is called. With synchronized processes: worker.run() -> starts the WF, a bit more complicated. processCtl:public boolean run() The process info is fetched with a complicated SQL-query: 11 columns of the table AD_process-AD_PInstance: column1: processname column2: Procedurename column3: Classname column4: AD_process_ID column5: isReport column6: isDirectPrint column7: AD_ReportView_ID column8: AD_Workflow_ID column9: static case-value column10: isServerprocess column11: jasperReport processes, Workflows, Jasper Reports, Reports and processes are managed here. processes call e.g. pi.setPrintPreview(!IsDirectPrint), which ends up in ReportCtl.start(), where processes for Order, Invoice, Shipment, Project, Page 38

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar

Payment and Dunnig are queried and maybe started; If the process does not fullfil any of these possibilities, the (normal) Report is executed. If the process activity is a workflow, it will be called: processCtl: startWorkflow (AD_Workflow_ID); For other alternatives, see processCtl. processCtl: private boolean startWorkflow(int AD_Workflow_ID) Remote-processes: server-connection is retrieved else wfprocess = processUtil.startWorkFlow(Env.getCtx(), m_pi, AD_Workflow_ID); processUtil: public static MWFprocess startWorkFlow(Properties ctx, processInfo pi, int AD_Workflow_ID) A workflow is instantiated wf=MWorkflow.get(ctx, AD_Workflow_ID) and started (in batch mode) wf.start(pi) or else delayed wf.startWait(pi) MWorkflow: public MWFprocess start(processInfo pi) A MWFprocess-Instance is created from processInfo The instance calls save() and startWork() . MWFprocess: public boolean startWork() workflow is fetched; AD_WF_Node_ID detected using the workflow getWorkflow().getAD_WF_Node_ID() An instance of MWFActivity is created: new MWFActivity (this, AD_WF_Node_ID) In this constructor a node will be instantiated using AD_WF_Node_ID. activity is started as thread: new Thread(activity).start() Have a look at MWFActivity, to see how it will proceed: It ends in processIt() of the referring BO.

MWFprocess: private MWorkflow getWorkflow() Call of MWorkflow.get (getCtx(), getAD_Workflow_ID()) MWorkflow: public static MWorkflow get (Properties ctx, int AD_Workflow_ID) a workflow is instantiated new MWorkflow (ctx, AD_Workflow_ID, null) MWorkflow: Constructor public MWorkflow (Properties ctx, int AD_Workflow_ID, String trxName) see description of the class Properties are set Nodes are loaded: loadNodes()

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 39

Developers Guide

The ADempiere Bazaar

Class MWFprocess
package org.compiere.wf public class MWFprocess extends X_AD_WF_process public class X_AD_WF_process extends PO // This class has persistancy functionality too. Properties private StateEngine m_state = null; private MWFActivity[]m_activities = null; private Mworkflow m_wf = null; private processInfo m_pi = null; private PO m_po = null; private String m_processMsg Methods checkActivities() The next Activity after the first completed Activity is started: startNext (activity, activities) public boolean startWork() Here is validated, whether the workflow can be started with its first node, whose activity is detected and executed. public void setAD_WF_Responsible_ID () The ID of the WF-Responsible is set.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 40

Developers Guide

The ADempiere Bazaar

Class Workflowprocessor
package org.compiere.server public class Workflowprocessor extends AdempiereServer Properties private Mworkflowprocessor m_model = null; // model private StringBuffer m_summary = new StringBuffer(); // Last Summary private Mclient m_client = null; // Client-Info methods Constructor public Workflowprocessor (MWorkflowprocessor model) m_model gesetzt; m_Client is set using the model doWork() called by AdempiereServer Calls wakeUp(). wakeUp() All interrupted, not processed activities of workflow nodes with inactive action are instantiated using an SQL-query For each of these activities: activity.setWFState (String WFState) private int sendAlertToResponsible (MWFResponsible responsible, ArrayList<Integer> list, MWFprocess process, String subject, String message, File pdf) private int sendEmail (MWFActivity activity, String AD_Message, boolean toprocess, boolean toSupervisor)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 41

Developers Guide

The ADempiere Bazaar

Class MWFNode
org.compiere.wf public class MWFNode extends X_AD_WF_Node public class X_AD_WF_Node extends PO // This class has also persistancy functionality Properties private ArrayList<MWFNodeNext> m_next private MColumn m_column = null; private MWFNodePara[] m_paras = null;

// next node // column description // process parameter

methods public String getActionInfo() called by toString() Action is fetched (goes until PO) In cases that Action is an Application process, called for information only return "process:AD_process_ID=" + getAD_process_ID() ( Possible actions (public static final Strings, defined in X_AD_WF_Node) ACTION_UserWorkbench = "B" ACTION_UserChoice = "C"; ACTION_DocumentAction = "D"; ACTION_SubWorkflow = "F"; ACTION_EMail = "M"; ACTION_Appsprocess = "P"; ACTION_AppsReport = "R"; ACTION_AppsTask = "T"; ACTION_SetVariable = "V"; ACTION_UserWindow = "W"; ACTION_UserForm = "X"; ACTION_WaitSleep = "Z"; ) These values must match the values of the Combobox for actions in the node of a workflow. public MWFNodePara[] getParameters() node parameter was fetched: m_paras = MWFNodePara.getParameters(getCtx(), getAD_WF_Node_ID()) getAD_Workflow_ID()get the WF-ID using X_AD_WF_Node until PO public MWorkflow getWorkflow() Mit MWorkflow.get(getCtx(), getAD_Workflow_ID()) public boolean isUserApproval() Is Action=ACTION_UserChoice?: ACTION_UserChoice.equals(getAction()) It is query, whether it was accepted: "IsApproved".equals(getColumn().getColumnName())

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 42

Developers Guide

The ADempiere Bazaar

Class processCtl
package org.compiere.apps (Client-Projekt) public class processCtl implements Runnable Manages Report&process. See its description Properties ASyncprocess m_parent; processInfo m_pi; // Properties and methods for process (transaction-ame, table-ID, AD_process_ID, AD_Workflow_ID, parameter, Record-ID, etc) private Trx m_trx; private Waiting m_waiting; private boolean m_IsServerprocess = false; Methods run() 11 columns of AD_process are read using a SQL-query and m_pi is filled: column1: processname column2: Procedurename column3: Classname column4: AD_process_ID column5: isReport column6: isDirectPrint column7: AD_ReportView_ID column8: AD_Workflow_ID column9: static case-value column10: isServerprocess column11: jasperReport Depends on the resulting m_pi-values different methods are executed: if e.g. AD_Workflow_ID>0, a workflow will be started: startWorkflow (AD_Workflow_ID) , a private method, which starts WF: processUtil.startWorkFlow(...). Alternatively it also could be started: Java-classes Starting with the method run() in processCtl, which calls startprocess(), the methods prepare() and doIt() of the class ImportInventory are called, which implement the behavior. In the report description of AD, this relation is explained more accurately Jasper Reports startprocess() normal Reports ReportCtl.start() Oracle-DB-Procedures startDBprocess() Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 43

Developers Guide

The ADempiere Bazaar

Constructor public static processCtl process(ASyncprocess parent, int WindowNo, IprocessParameter parameter, processInfo pi, Trx trx) An instance of MPInstance is created: MPInstance instance (mithilfe von pi) Parameters are read ( save() ) With synchronized processes the process is executed immediately run()

Class processUtil org.adempiere.util public final class processUtil No important properties (just the Logger) Just 3 methods public static boolean startDatabaseProcedure(processInfo processInfo, String ProcedureName, Trx trx) Procedure is executed public static boolean startJavaprocess(processInfo pi, Trx trx) the class name is read from pi, then the class is obtained. Finally the class will be instantiated as process. public static MWFprocess startWorkFlow(Properties ctx, processInfo pi, int AD_Workflow_ID) a workflow is instantiated: wf = MWorkflow.get (ctx, AD_Workflow_ID) WF is started: wf.start(pi)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 44

Developers Guide

The ADempiere Bazaar

Invoice-Preview
It is explained here, because it has relation to processCtl. Explanation: Print formats can be allocated to Business Partners, Document Types and ad_printform.

Values in Table of a invoice previews


AD_TABLE_ID=318 ( C_INVOICE) AD_process_ID: 116 (Rpt C_Invoice) AD_PRINTFORMAT_ID: 1000071 (Standard Invoice Header). Its column ad_table_id references to a table n C_Invoice_Header_v, which represents a invoice print view

Calling sequence to show the Invoice-Preview


APanel.actionPerformed() APanel.cmd_print() processCtl.process() : AD_TABLE_ID=318 ( C_INVOICE), AD_process_ID: 116 processCtl.start() Thread.start() : : The process is executed: processCtl.run: ReportCtl.start() ReportCtl.startDocumentPrint() ReportEngine.get() (Class method) ReportEngine-Constructor ReportEngine.getQuery() ReportEngine.setPrintData() DataEngine.getPrintData() DataEngine.getPrintDataInfo() back in getPrintData() loadPrintData() is called, where the SQL-query for data acquisition is executed backin ReportEngine.get() ReportEngine of the calling method startDocumentPrint() is returned backin startDocumentPrint(): ReportCtl.CreateOutput() The report is shown, considering whether direct printing or preview is selected. In the latter case the Code der Viewer takes over: ReportCtl.preview() ReportViewerProvider provider = getReportViewerProvider(); provider.openViewer(re); SwingViewerProvider. openViewer() -- SwingViewerProvider inherited from ReportViewerProvider Viewer-Constructor() ReportEngine.getView() Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 45

Developers Guide

The ADempiere Bazaar

ReportEngine.layout() LayoutEngine-Constructor() LayoutEngine.layout() Different handling, depending on whether PrintForm is form or table LayoutEngine.layoutForm() read configuration of Print Format. Position, max. Width, max. Hight, etc. of each Element is identified. The local variable element contains Print Format. In viewer, the button Print triggers: Viewer.actionPerformed () Viewer.cmd_print() ReportEngine.print() -- the print is started

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 46

Developers Guide Now the same in more detail ( partly from an existing example): THE BUTTON PRINT PREVIEW IN INVOICE WAS PRESSED HERE Apanel.actionPerformed(): PrintPreview - 16 Apanel.cmd_print() ID=116 [12] AD_TABLE_ID=318 ( C_INVOICE) AD_process_ID= 116 (Rpt C_Invoice) processInfo pi: pi.m_Title=Invoice (Customer) SuperUser@Company Name pi.m_AD_process_ID=116 pi.m_Record_ID=1004868

The ADempiere Bazaar

processCtl.process() WindowNo=2 - processInfo[Invoice (Customer) SuperUser@company_name [linuxjupiter{linux-jupiter-orcl-adempiere}], process_ID=116, Record_ID=1004772, Error=false,Summary=,Log=0] [12] (Record_id is id of the invoice) (process_ID=116 ist Rpt C_Invoice)

ReportCtl.start: start() processInfo[Invoice Print ,process_ID=116,AD_PInstance_ID=1004057,Record_ID=1004772, Error=false, Summary=, Log=0] [42] unsynchronized process AD_PInstance_ID changes always in AD_PInstance the process ID (here 116) and Record ID (here 1004868) are saved, so the value is not lost on reload.

Thread is executed processCtl.run() with a SQL query on AD_PInstance the configuration of AD_process is read in: Name, ProcedureName,ClassName, AD_process_ID, isReport (=Y), isDirectPint (=Y), ReportView-ID (=keine), Workflow-ID (=keine), isServerprocess(=N) and JasperReport-Name (none). These values are assigned to the classvariable m_pi. Client- and User-ID do already have the correct values After that, it checks what is about to be executed (Workflow, Report, process). As print invoice is a report, the next methode is called. ReportCtl.start() Hard-coded dependancy: depending on the process_id, a method will be called. For standard reports the the call is: startStandardReport(pi). A Report-Engine instance is c reated and CreateOutput(re, pi.isPrintPreview()) is called. ReportEngine-Instance is Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 47

Developers Guide

The ADempiere Bazaar

forwarded. startStandardReport(pi) calls immediately ReportEngine.get(). For invoice (process_ID()= 116) is called:

ReportCtl.startDocumentPrint() calls immediately ReportEngine.get() . In ReportEngine.get() a SQL query is executed depending on the type (Check, Dunning, Remmitance, Project, RfQ, Order/Invoice/Shipment). Here the SQL query for an invoice is executed, because the type invoice (=2) was passed through since ReportCtl.start().
Side explanation Print Format begin There are 3 places in Adempiere where a Print Format can be defined: 1.- At Business Partner 2.- In register "Client" a Print Format can be set 3.-Window Document Type: Table c_doctype has besides the Doc Base Type a Print Format. Window Print Form (default): AD_PRINTFORM is a table, which has predefined Print Formats (for Invoice, Order, Shipment) as well as mailtexts (for Invoice, Order, Shipment, Projects...) for each Client and Organization. As an example, in AD_PRINTFORM the invoice print format id=1000071 is declared for a customer MyCustomer. This ID is in table AD_PRINT_FORMAT "Invoice_Header", its column ad_table_id refers to table called C_Invoice_Header_v, which represents an Invoice Print View . If neither business partner nor document type is declared, it will default to ad_printform. Side explanation Print Format end

Is the printing of an order required and there is already an invoice, then this invoice is printed (else the order is printed). ReportEngine.getDocumentWhat() is called for this. It changes the type and Record-ID to Invoice. The rest is done in ReportEngine.get(). Neededdocument information for printing is fetched with a SQL query. TheSQL query contains a Join of tables c_invoice, ad_printform, ad_client, c_doctype and c_bpartner, where Print Format of Order, Shipment, Projekt, Remmitance and Invoice is identified. In the case of an Invoice: Priority # 1: BPartner; Priority # 2: DocType: Priority #3: PrintFormat (ad_printform). Additionally the number of copies (either from Business Partner or Document Type), Document-No. of Invoice and BP-ID are fetched: SELECT pf.Order_PrintFormat_ID,pf.Shipment_PrintFormat_ID, COALESCE (bp.Invoice_PrintFormat_ID, dt.AD_PrintFormat_ID, pf.Invoice_PrintFormat_ID), pf.Project_PrintFormat_ID, pf.Remittance_PrintFormat_ID, c.IsMultiLingualDocument, bp.AD_Language, COALESCE(dt.DocumentCopies,0)+COALESCE(bp.DocumentCopies,1), Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 48

Developers Guide

The ADempiere Bazaar

dt.AD_PrintFormat_ID,bp.C_BPartner_ID,d.DocumentNo FROM C_Invoice d INNER JOIN AD_Client c ON (d.AD_Client_ID=c.AD_Client_ID) INNER JOIN AD_PrintForm pf ON (c.AD_Client_ID=pf.AD_Client_ID) INNER JOIN C_BPartner bp ON (d.C_BPartner_ID=bp.C_BPartner_ID) LEFT OUTER JOIN C_DocType dt ON (d.C_DocType_ID=dt.C_DocType_ID) WHERE d.C_Invoice_ID=? AND pf.AD_Org_ID IN (0,d.AD_Org_ID) ORDER BY pf.AD_Org_ID DESC Result (for example) is a line with following values: ORDER_PRINTFORMAT_ID 1000069 SHIPMENT_PRINTFORMAT_ID 1000073 PRINTFORMAT_ID 1000100 PROJECT_PRINTFORMAT_ID REMITTANCE_PRINTFORMAT_ID 1000077 ISMULTILINGUALDOCUMENT Y AD_LANGUAGE DOCUMENTCOPIES 2 AD_PRINTFORMAT_ID C_BPARTNER_ID 1000317 DOCUMENTNO VCF_633487_2007

Ad_printformat_id, c_bpartner_id, beleg-Nr and number of copies (if empty, it is set to 1) are extracted using the SQL and saved in variables. This is info about the print, not printable data (yet). An instance of each MPrintFormat, MQuery and PrintInfo is created. The local variable query assigned to the constructor. I can e.g. be C_Invoice_Header_v.C_Invoice_ID=1004868" and is directly created using the invoice type (Result: C_Invoice_Header_v at Invoice) and the document type + -ID (Result: C_Invoice_ID=1004868).
Side explanation for DOC_TABLES begin The local variable query uses an indexing of DOC_TABLES for finding the table : DOC_TABLES[type]. By defining the variables TABLES the table name can be found: DOC_TABLES = new String[] {"C_Order_Header_v", "M_InOut_Header_v", "C_Invoice_Header_v", "C_Project_Header_v","C_RfQResponse_v","C_PaySelection_Check_v", "C_PaySelection_Check_v", "C_DunningRunEntry_v" }; So, if type==2 (Invoice), the resulting table becomes C_Invoice_Header_v. Side explanation for DOC_TABLES end

At the end, a ReportEngine is instantiated in ReportEngine.get(), where class variables are assigned to the extracted instances of the classes MPrintFormat, MQuery and PrintInfo . The instantiated ReportEngine is returned to the calling method. What will happens when the constructor of ReportEngine is called? : Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 49

Developers Guide

The ADempiere Bazaar

Example: ReportEngine.<init>(Constructor): MPrintFormat[ID=1000071,Name=Invoice_Header,Language=Language=[Espaol (El Salvador), Locale=es_SV, AD_Language=es_SV, DatePattern=DD/MM/YYYY, DecimalPoint=true],Ite ms=56] -- C_Invoice_Header_v.C_Invoice_ID=1004772 [42] ReportEngine.getQuery() ReportEngine.setPrintData(). Instantiates a DataEngine and calls getPrintData(). DataEngine.getPrintData() Firstly, the name of the table is identified, because we are not in a report view (SELECT TableName FROM AD_Table WHERE AD_Table_ID=516;): in AD it is the table C_Invoice_Header_v. The table name is changed to C_Invoice_Header_vt here. Beware: there is no table C_Invoice_Header_vt in AD; just C_Invoice_Header_v !! Query is changed to: C_Invoice_Header_vt.C_Invoice_ID=1004868 getPrintDataInfo() is called (a huge method). Next the data is fetched with loadPrintData() using another huge method. First, let's concentrate on getPrintDataInfo(). DataEngine.getPrintDataInfo() In this method a SQL query for data extraction is created in variable finalSQL. The columns for the needed SQL are taken among others from PrintFormat. getPrintDataInfo() is called with parameters like: Name of report: Invoice_Header TableName=C_Invoice_Header_vt Query=C_Invoice_Header_vt.C_Invoice_ID=1004772 AND C_Invoice_Header_vt.AD_Language='es_SV' Contains the WHERE-condition of the SQL that shall be built in this method. Format=MPrintFormat[ID=1000071, Name=Invoice_Header, Language=Language=[Espaol (El Salvador), Locale=es_SV, AD_Language=es_SV, DatePattern=DD/MM/YYYY, DecimalPoint=true], Items=56] Column order AD_Column_ID=7483 A SQL is executed with ad_printformat_id as parameter (extracted from variable Format). Columns of Print Format, PrintFormatItem, AD_Column etc. are extracted so a SQL like this is built:
String sql = "SELECT c.AD_Column_ID,c.ColumnName," + "c.AD_Reference_ID,c.AD_Reference_Value_ID," + "c.FieldLength,c.IsMandatory,c.IsKey,c.IsParent," + "COALESCE(rvc.IsGroupFunction,'N'),rvc.FunctionColumn," + "pfi.IsGroupBy,pfi.IsSummarized,pfi.IsAveraged,pfi.IsCounted, " + "pfi.IsPrinted,pfi.SortNo,pfi.IsPageBreak, " // // // // // // 1..2 3..4 5..8 9..10 11..14 15..17

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 50

Developers Guide

The ADempiere Bazaar


+ "pfi.IsMinCalc,pfi.IsMaxCalc, " // 18..19 + "pfi.isRunningTotal,pfi.RunningTotalLines, " // 20..21 + "pfi.IsVarianceCalc, pfi.IsDeviationCalc, " // 22..23 + "c.ColumnSQL " // 24 + "FROM AD_PrintFormat pf" + " INNER JOIN AD_PrintFormatItem pfi ON (pf.AD_PrintFormat_ID=pfi.AD_PrintFormat_ID)" + " INNER JOIN AD_Column c ON (pfi.AD_Column_ID=c.AD_Column_ID)" + " LEFT OUTER JOIN AD_ReportView_Col rvc ON (pf.AD_ReportView_ID=rvc.AD_ReportView_ID AND c.AD_Column_ID=rvc.AD_Column_ID) " + "WHERE pf.AD_PrintFormat_ID=?" // #1 + " AND pfi.IsActive='Y' AND (pfi.IsPrinted='Y' OR c.IsKey='Y' OR pfi.SortNo > 0) " + "ORDER BY pfi.IsPrinted DESC, pfi.SeqNo";

The result of the SQL execution are the columns as defined in AD and looks like this: AD_COLUMN_ID COLUMNNAME AD_REFERENCE_ID (Im Code Display Type) 7652 BPValue 10 (Text) 7466 C_Order_ID 30 (Search) 7463 DateInvoiced 15 (Date) 7475 Name 10 (Text) 7448 C_Location_ID 21 (Location (Address) ) 7586 PaymentTerm 10 (Text) 7460 M_PriceList_ID 19 (Table Direct) 7483 DocumentNo 10 (Text) and other columns: AD_REFERENCE_VALUE_ID, FIELDLENGTH, ISMANDATORY, ISKEY, ISPARENT, COALESCE(RVC.ISGROUPFUNCTION,'N'), FUNCTIONCOLUMN, ISGROUPBY, ISSUMMARIZED, ISAVERAGED, ISCOUNTED, ISPRINTED, SORTNO, ISPAGEBREAK, ISMINCALC, ISMAXCALC, ISRUNNINGTOTAL, RUNNINGTOTALLINES, ISVARIANCECALC, ISDEVIATIONCALC, COLUMNSQL Sort columns in Print Format and all columns, that are marked as key in the SQL, are included in addition to those selected in the PrintFormat (but marked as not printed). Important: the configuration of the columns is related to their definition at C_Invoice_Header_v, not by its original table!!! This means a column C_Order_id is not a key in C_Invoice_Header_v, but in the table C_Order. On the one hand these lines are analyzed in a loop and saved line by line in a PrintDataColumn instance (AD_Column_ID, ColumnName, AD_Reference_ID, FieldLength, orderName, isPageBreak). On the other hand this information is used to generate SQL for the extraction of the printing data. This SQL, which is created in getPrintDataInfo() and saved in variable finalSQL looks e.g. like this:
SELECT (SELECT C_Order.DocumentNo||' - '||TRIM( TO_CHAR( C_Order.DateOrdered,

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 51

Developers Guide

The ADempiere Bazaar

'DD/MM/YYYY')) FROM C_Order WHERE C_Invoice_Header_vt.C_Order_ID=C_Order.C_Order_ID) AS AC_Order_ID, // This was the first column, called AC_Order_ID C_Invoice_Header_vt.C_Order_ID, // The last columns are just displayed instead of c_order_id; see explanation beneath C_Invoice_Header_vt.DateInvoiced, C_Invoice_Header_vt.Name, B.City||'.' AS Baddress, // see explanation C_Invoice_Header_vt.C_Location_ID, // see explanation C_Invoice_Header_vt.PaymentTerm, // for invoice, see explanation (SELECT M_PriceList.Name FROM M_PriceList WHERE C_Invoice_Header_vt.M_PriceList_ID=M_PriceList.M_PriceList_ID) AS CM_PriceList_ID, // This is another column, called CM_PriceList_ID C_Invoice_Header_vt.M_PriceList_ID FROM C_Invoice_Header_vt LEFT OUTER JOIN C_Location B ON (C_Invoice_Header_vt.C_Location_ID=B.C_Location_ID) WHERE C_Invoice_Header_vt.C_Invoice_ID=1006390 AND C_Invoice_Header_vt.AD_Language='es_SV' AND C_Invoice_Header_vt.AD_Client_ID IN (0,1000001) AND B.C_Location_ID NOT IN ( SELECT Record_ID FROM AD_Private_Access WHERE AD_Table_ID = 162 AND AD_User_ID <> 100 AND IsActive = 'Y' ) ORDER BY C_Invoice_Header_vt.DocumentNo

A PrintData instance with column description, finalSQL and table (C_Invoice_Header_vt) is created and returned.
Side explanation begin Which field will be shown if there are IDs in Print Format? A) As example: c_order_id in invoice print. Each column is described in the AD among other things with the field isIdentifier. All columns having this field=Y are displayed when the ID field is present. This way, isIdentifier is used as an information source for the whole object, because just the ID itself is not very useful. In getPrintDataInfo() it is queried during the column analysis, whether the column is a search field (ad_reference_id=30). If true, the following will be called: MlookupFactory.getLookup_TableDirEmbed(), a SQL query is called: SELECT c.ColumnName,c.IsTranslated,c.AD_Reference_ID,c.AD_Reference_Value_ID FROM AD_Table t INNER JOIN AD_Column c ON (t.AD_Table_ID=c.AD_Table_ID) AND c.IsIdentifier='Y' WHERE TableName='C_Invoice' // or the corresponding table ORDER BY c.SeqNo; A column like AC_Order_ID is built with this. In c_order, the columns DocumentNo and DateOrdered are marked as identifier ( isIdentifier =Y), which yields this output: SELECT C_Order.DocumentNo||' '||TRIM( TO_CHAR( C_Order.DateOrdered, 'DD/MM/YYYY')) FROM C_Order

where

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 52

Developers Guide
WHERE C_Invoice_Header_vt.C_Order_ID=C_Order.C_Order_ID. Next, the normal SQL code is created: C_Invoice_Header_vt.C_Order_ID. B)At C_Invoice the columns are: DocumentNo, DateInvoiced and GrandTotal. The SQL is then: SELECT C_Invoice.DocumentNo||' '||TRIM(TO_CHAR(C_Invoice.DateInvoiced,'DD/MM/YYYY'))||' '||TRIM(TO_CHAR(C_Invoice.GrandTotal,'999G999G999G990D00')) FROM C_Invoice WHERE C_Invoice_Header_vt.C_Invoice_ID=C_Invoice.C_Invoice_ID Next the normal SQL code is created: C_Invoice_Header_vt.C_Invoice_ID.

The ADempiere Bazaar

C) If the reference type of the column = Location, then the SQL in the programm is changed in a way that B.City||'.' AS Baddress is returned. Next the normal SQL-Code is created: C_Invoice_Header_vt.C_Location_ID. Something similar will happen if the reference type the column = Account, Locator oder PAttribute. Side explanation end.

End of DataEngine.getPrintDataInfo(). back in DataEngine.getPrintData(), loadPrintData() is called immediately, where the SQL is executed. DataEngine.loadPrintData() The result of the SQL is a single or multiple lines. Example with a single line: BPVALUE 0191 (Key) AC_ORDER_ID 701517 C_ORDER_ID 1004302 DATEINVOICED 22-AUG-07 NAME NAME OF A COMPANY NAME2 FARMACIA DEL PUEBLO DUNS 126615-2 BADDRESS SANTA ROSA DE LIMA. C_LOCATION_ID 1000300 TAXID DESCRIPTION COMPRA/VENTA DE MEDICINAS SALESREP_NAME TORRES CARBALLO, RIGOBERTO PAYMENTTERMNOTE 30 DIAS CC_INVOICE_ID VCF_633487_2007 - 22/08/2007 - 302.98 C_INVOICE_ID 1005618 TOTALLINES 268.12 In loadPrintData() all lines are read in a big loop with all lines and there the columns are analyzed singly within a small loop . The variable rowNo counts the rows, counter the columns (the last references to the next column). Small loop: Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 53

Developers Guide

The ADempiere Bazaar

The variable pdc (PrintDataColumn) contains infos about the columns that needs to be edited (using indexing of the input parameter pd). m_ad_Column_id is the definition of the column in ad_column, within the table C_Invoice_Header_v. m_alias is the name of the column in SQL, e.g. AC_Order_ID Alias is only used if the column is concatenated, caused by the combination IDidentifier. Using aliases the next column is fetched immediately, because it follows suit in the SQL (see SQL of getPrintDataInfo()) . m_column_name: is the column that can be found in Print Format, e.g. C_Order_ID. the actual column depends amongst other things whether it is a ID and also which fields are called as identifier. Boolean values, long texts, DateTime-values, strings and numbers) are managed At the end of the small loop an object pde (PrintDataElement) is created, which contains the name of the column, content, whether it contains a key or page break. The object pde is added to pd and added to the variable m_group. m_column_name: is the column, that is set in Print format, e.g.: C_Order_ID m_display_type: = AD_REFERENCE_ID (see previously in text) m_value Contains the pair in concatenated columns (otherwise just m_value) m_key e.g. 1000300 The ID of the related table. For c_invoice_id it is c_invoice, at c_location_id it is table c_location, etc. m_value, e.g. SANTA ROSA DE LIMA.. In loadPrintData() the lines are counted programmatic (starting with the line, with the comment: // Add Total Lines The calculation of functions like Count, Mean, Sum, Minimum, Maximum, Derivation etc. are started at // Check last Group Change in loadPrintData() with the call m_group.getValue() PrintDataGroup.getValue (): PrintDataFunction.getValue(char function).

Back in ReportEngine.get() the ReportEngine of the calling method startDocumentPrint() is returned. startDocumentPrint(): CallCreateOutput() : The report is displayed depending on whether it is direct printing or preview. In the latter case the viewer is shown with this code: ReportViewerProvider provider = getReportViewerProvider(); provider.openViewer(re) The layout is created as consequence of provider.openViewer(re) using re.getView(). The variable re is of the class ReportEngine and contains the SQL for data extraction. Multiple methods are processed, amongst others LayoutEngine.layout(), LayoutEngine.layoutForm(). Side explanation begin LayoutEngine Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 54

Developers Guide

The ADempiere Bazaar

The class LayoutEngine has 3 methods that are important for printing layout() In the constructor of LayoutEngine the parameter Data that is used amongst other things for data extraction is submitted and the m_data is saved. m_data contains m_sql, which is used for data extraction The SQL for the aging report is: SELECT T_Aging.DueAmt, A.Value||'-'||A.Name AS Aname, T_Aging.SalesRep_ID, (SELECT C_SalesRegion.Name FROM C_SalesRegion WHERE T_Aging.C_SalesRegion_ID=C_SalesRegion.C_SalesRegion_ID) AS BC_SalesRegion_ID, T_Aging.C_SalesRegion_ID, T_Aging.BPValue, T_Aging.SalesRep_Name, T_Aging.SalesRegionName, T_Aging.DateInvoiced, (SELECT C_BPartner_Location.Name FROM C_BPartner_Location WHERE T_Aging.C_BPartner_Location_ID=C_BPartner_Location.C_BPartner_Location_ID) AS CC_BPartner_Location_ID, T_Aging.C_BPartner_Location_ID, T_Aging.LocationName, (SELECT C_BPartner.Value||' - '||C_BPartner.Name FROM C_BPartner WHERE T_Aging.C_BPartner_ID=C_BPartner.C_BPartner_ID) AS DC_BPartner_ID, T_Aging.C_BPartner_ID, SELECT C_BP_Group.Name FROM C_BP_Group WHERE T_Aging.C_BP_Group_ID=C_BP_Group.C_BP_Group_ID) AS EC_BP_Group_ID, T_Aging.C_BP_Group_ID FROM T_Aging LEFT OUTER JOIN AD_User A ON (T_Aging.SalesRep_ID=A.AD_User_ID) WHERE T_Aging.AD_PInstance_ID=1017227 AND T_Aging.C_BPartner_ID=1000598.0 AND T_Aging.IsListInvoices='N' AND T_Aging.IsSOTrx='Y' AND T_Aging.AD_Client_ID IN (0,1000001) AND A.AD_User_ID NOT IN ( SELECT Record_ID FROM AD_Private_Access WHERE AD_Table_ID = 114 AND AD_User_ID <> 100 AND IsActive = 'Y' ) Also if the condition is C_BPartner_ID=1000598.0 the correct value is returned. layoutForm() layoutTable() Side explanation end The data of an embedded print format, like e.g. Invoice Line Tax, are extracted like: Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 55

Developers Guide LayoutEngine.layout() LayoutEngine.layoutForm(), LayoutEngine.includeFormat() DataEngine.getPrintData() is called (see above) Variable PrintData includedData contains data for printing. Call of LayoutEngine.layoutTable() -- e.g. using tables The data are analysed here. If it is a field, the method LayoutEngine.createFieldElement()is called in LayoutEngine.layoutForm(). LayoutEngine.createFieldElement() Here the output is done Conversion into string Display of ID elements Numbers are called as words at output: Msg.getAmtInWords (m_format.getLanguage(), stringContent) This depends on the selected language. For Spanish the method AmtInWords_ES.getAmtInWords() is called. Color

The ADempiere Bazaar

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 56

Developers Guide

The ADempiere Bazaar

PrintFormat
Table AD_PRINT_FORMAT contains all entries to fill the equal-named window

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 57

Developers Guide

The ADempiere Bazaar

class PrintData
Container for actual data printing. Here are explained only class variables, but not methods. class variables PrintDataColumn[] m_column_info An array of elements, which describe the columns that are printed PrintDataColumn is also a container with only get methods, a constuctor and the following class variables: private int m_AD_Column_ID; private String m_columnName; private int m_displayType; private int m_columnSize; private String m_alias; private boolean m_pageBreak; Properties m_ctx -- Context String m_name:

-- Name of the print format

ArrayList<Object> m_nodes -- Array of PrintDataElement Arrays Extra lines as sum. PrintDataElement Klassenvariablen: private String m_columnName; private int m_displayType; private boolean m_isPageBreak; private boolean m_isPKey; m_value; ArrayList<ArrayList<Object>> mrows -- Array of PrintDataElement arrays Contains the real data as rows and columns. m_sql Data is extracted with this e.g.
SELECT

C_Invoice_LineTax_vt.ProductValue, C_Invoice_LineTax_vt.QtyEntered, C_Invoice_LineTax_vt.Name, C_Invoice_LineTax_vt.Discount, C_Invoice_LineTax_vt.PriceEntered, C_Invoice_LineTax_vt.LineNetAmt, C_Invoice_LineTax_vt.C_InvoiceLine_ID was automaticly fetched FROM C_Invoice_LineTax_vt WHERE C_Invoice_LineTax_vt.C_Invoice_ID=1005618 AND C_Invoice_LineTax_vt.AD_Language='es_SV' AND C_Invoice_LineTax_vt.AD_Client_ID IN (0,1000001) Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 58

Developers Guide ORDER BY C_Invoice_LineTax_vt.Line m_TableName, e.g. C_Invoice_LineTax_vt

The ADempiere Bazaar

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 59

Developers Guide

The ADempiere Bazaar

Workflow (WF) im AD
WF-Types are found this way (by the way: How are combo boxes designed((Author) ) Login as System Admin Menu, Window Workflow Zoom at field Window In Window, Tab & Field with name Workflow, Tab Workflow, Field Workflow Type zoom to column WorkflowType. Table&Column with name AD_Workflow to Tab Column, zoom at Field Reference Key (is AD_Workflow Type) Select Reference with namen AD_Workflow Type, Tab List Validation in window. values General, Document process and Document Value can be found there.

There are three WF-Types in Adempiere General (common process) G WFs, that can be seen and used as normal user Accounting Setup BP Setup Price List Setup Product Setup Request Setup Requisition Setup Sales Setup Tax Setup etc. Document process process_Cash process_Inventory process_Invoice process_Journal process_Journal Batch process_Movement process_Order process_Payment process_Requisition etc. P

Document Value V No entries (maybe in Garden World?)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 60

Developers Guide

The ADempiere Bazaar

Workflow-Window
Tab Workflow Workflow Type (see above) Data Access Level (All, Organisation, Client, etc) Start Node Workflow processor (it is empty in all entries; there is just only one: System Workflow processor) Tab Node Combo Box WF Responsible ( Invoker, Organisation) Combo Box Start Mode (Automatic, Manual) Combo Box Join Element (XOR, AND) Combo Box Split Element (XOR, AND) Combo Box Action (Apps process, Apps Report, Apps Task, Document Action, Email, Set Variable, Sub Workflow, User Choice, User Form, User Window, Wait (Sleep). They seem equal to the possibilities in MWFActivity: performWork(). Task, Sub Workflow, User Workbench, User Form and User Window are offered, but not implemented. Fields are offered depending on the selected Action : in User Window it is a combo box window with all possible windows; whereas in Document Action, it is a combo box with entries from the reference _Document Action (Approve, Close, Complete, Invalidate, Post, Prepare, Void, Unlock etc. They are equal to DocAction-methods in source code of BOs). Tab Transition Next Node: next node to execute There are multiple node possible to be the next. One of them is Standard. Operation of the node defines when which node is executable. Tab Condition rarely used And/Or column Operation (+, -, etc.) Value Conclusion: In Tab Workflow is defined which node is started. The Node defines for actions, which action is executed. The option are close, prepare, etc. for an action of type document action. This results in a call of the equal named BO method und a change of state. Transition defines which node is next. ERM Static Is all what is defined in the AD An AD_WORKFLOW can have multiple AD_WF_NODEs. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 61

Developers Guide Dynamic processes are created during the execution an AD_WF_ACTIVITYs can have a AD_WF_NODE an AD_WORKFLOW can have multiple AD_WF_processs an AD_WF_process can have multiple AD_WF_ACTIVITYs

The ADempiere Bazaar

Actions in Tab Node: Reference: WF_Action List Validation Apps process Apps Report Apps Task Document Action Email Set Variable Sub Workflow User Choice User Form User Window User Workbench Wait (Sleep)

Search Key P R T D M V F C X W B (inactive) Z

Workflow documentation for BOs: The history of the workflow, in which this BO is involved, can be inspected using the icon Active Workflows (two squares connected with a arrow) in any of the BO lines, which implement DocAction. So it can be seen where the workflow is. Loading: APanel: actionPerformed (ActionEvent e) is the button dispatcher. For the workflow button AEnv: public static void startWorkflowprocess (int AD_Table_ID, int Record_ID) is called. A dataset of AD_WF_process is read in here (for the current Tabelle and its current dataset). Saving: Object X_AD_WF_process, father class of MWFprocess is a X_AD_WF_process. But X_AD_WF_process is subclass of PO too, so that intances of it are saved. Somewhere in the constructor it is saved and refreshed.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 62

Developers Guide

The ADempiere Bazaar

Example of a workflow: Order Here it is the other way around: starting with the window and end with the source code. Sales Order of menu Zoom Window Sales Order, Tab Order, Feld Table (Content: C_Order). Table C_Order, Tab Column, column name processing Field Reference is a button; Field process references to C_Order process. Zoom into this: feld Workflow has the content Process_Order (one of the defined general workflows)

Window Workflow Editor Reads nodes of a workflow, transition to the next node, join constraints and split constrains and presents it in a graphical visualisation. XOR as a join-condition means that the first possible action is selected.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 63

Developers Guide

The ADempiere Bazaar

Window Report & Process


The dispatcher ist processCtl.run(). Characteristics of reports/processes: Document Action Already explained in processCtl Java-class The class of a report definition in AD can be found in the field Class Name, which is executed (in this example org.compiere.process.ImportInventory). Its methods prepare() and doit() control the behavior of the buttons. How are prepare() and doit() of the class ImportInventory called? processCtl manages process calls as explained. After detecting that it is a java call, run() in processCtl calls method startprocess() processCtl: private boolean startprocess () Calls (for local processes) startJavaprocess(m_pi, m_trx) processUtil: public static boolean startJavaprocess(processInfo pi, Trx trx) calls a processCall-method: startprocess(Env.getCtx(), pi, trx) interface processCall declares this method, that some class must implement: public boolean startprocess (Properties ctx, processInfo pi, Trx trx) This is done by Svrprocess.

Because java-class ImportInventory is defined as following: public class ImportInventory extends Svrprocess and Svrprocess this way: public abstract class Svrprocess implements processCall Especially Svrprocess implements method startprocess() of processCall. So Svrprocess implements startprocess(). The following happens inside: private method process() is called, where immediately and after each other prepare() and doIt() is called. Both are definined as abstract in Svrprocess, so both must be defined in the subclass. This happens in ImportInventory, and so these methods are executed by ImportInventory. protected void prepare() Reads the calling parameter from process info variable m_pi and assigns Properties with it. protected String doIt() The process is implemented. Returns a String, which is used for among other things fault handling and messaging.

This way AD and programm code interact during java class-calls . For more explaination, see description of the class processCtl.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 64

Developers Guide

The ADempiere Bazaar

Oracle-Procedure Workflow Report Jasper Report Call chain for parameter displaying in Report&Process calls Here is just explained how parameters in AD are displayed according to its definition. How Report&Process works is explained in the previous section.

AMenuStartItem.startprocess() processDialog.init() Fetches the process info processParameterPanel.init() Fertches all call parameter of the process

org.compiere.apps org.compiere.apps

org.compiere.apps

SELECT p.Name, p.Description, p.Help, p.AD_Reference_ID, p.AD_process_Para_ID, p.FieldLength, p.IsMandatory, p.IsRange, p.ColumnName, p.DefaultValue, p.DefaultValue2, p.VFormat, p.ValueMin, p.ValueMax, p.SeqNo, p.AD_Reference_Value_ID, vr.Code AS ValidationCode FROM AD_process_Para p LEFT OUTER JOIN AD_Val_Rule vr ON (p.AD_Val_Rule_ID=vr.AD_Val_Rule_ID) WHERE p.AD_process_ID=? AND p.IsActive='Y' // z.B.238 ORDER BY SeqNo

createField() is called for each parameter. processParameterPanel.createField() org.compiere.apps GridFieldVO.createParameter() is called first. Next, the constructor new GridField (voF) is called, where loadLookup() is called. Variable vot.AD_Column_ID references to ID of the Parameter in the table AD_process_Para and not ID in AD_Column! GridFieldVO.createParameter() org.compiere.model A constructor for GridFieldVO is called. Data like ColumnName, Name, Description, AD_Reference_ID, FieldLength, etc. are read from the parameter. Class variable lookupInfo contains possible lookup information. GridFieldVO.initFinish() org.compiere.model When field Reference of report&process-parameters equals List, Table, TableDirect or Search: Lookup-handling. Field Reference is extracted from table AD_Reference, where e.g. List and Search have IDs 17 and 30. Calling getLookupInfo(). The result is saved in MLookupInfo lookupInfo (just a variable, no data), a class variable of GridFieldVO. MLookupFactory.getLookupInfo() org.compiere.model Different handling, depending on List, Table, TableDirect or Search. Lists: getLookup_List(). Table or Search and field Reference Value with valid value: getLookup_Table(). The field Reference Value is extracted from table AD_Reference, where e.g. AD_User Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 65

Developers Guide SalesRep has ID 190. Otherwise (including TableDirect), the call is getLookup_TableDir().

The ADempiere Bazaar

The SQL is completed furthermore after the lookup call (e.g. to add Client_ID condition and the role condition). An object with the information of this parameter is returned to initFinish(). MLookupFactory.getLookup_Table() A SQL query is sent. org.compiere.model

SELECT t.TableName,ck.ColumnName AS KeyColumn,cd.ColumnName AS DisplayColumn,rt.IsValueDisplayed, cd.IsTranslated,rt.WhereClause,rt.OrderByClause,t.AD_Window_ID,t.PO_Window_ID, t.AD_Table_ID FROM AD_Ref_Table rt INNER JOIN AD_Table t ON (rt.AD_Table_ID=t.AD_Table_ID) INNER JOIN AD_Column ck ON (rt.AD_Key=ck.AD_Column_ID) INNER JOIN AD_Column cd ON (rt.AD_Display=cd.AD_Column_ID) WHERE rt.AD_Reference_ID=190 -- "AD_User - SalesRep " AND rt.IsActive='Y' AND t.IsActive='Y'

result is a line with e.g. the following content of the AD-column SalesRep, which is saved in variables: TABLENAME AD_User KEYCOLUMN AD_User_ID DISPLAYCOLUMN Name ISVALUEDISPLAYED Y ISTRANSLATED N WHERECLAUSE EXISTS (SELECT * FROM C_BPartner bp WHERE AD_User.C_BPartner_ID=bp.C_BPartner_ID AND bp.IsSalesRep='Y') This where-Clause will not work correctly, if it is executed in SQL Developer. Within the SQL query, which is build later it causes like the following:
SELECT * FROM C_BPartner bp inner join AD_User adu on (adu.C_BPartner_ID=bp.C_BPartner_ID) WHERE bp.IsSalesRep='Y'

In this example all users are shown, which are marked as SalesRep in the C_BPartner-table . ORDERBYCLAUSE AD_WINDOW_ID PO_WINDOW_ID AD_TABLE_ID -108 -114 (the same asWindow Namens Task) (the same as table Namens User/Contact)

AD_RefTable contains attributes of instances of the table AD_Reference, specially the table referred to, their key- and display columns, as well as a constrain asSQL, which must be valid for this reference. These Feld can be found in Adempiere in tab Table Validation of window Reference. This makes the result of the last query understandable. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 66

Developers Guide

The ADempiere Bazaar

If KeyColumn does not end with _ID, NULL will be inserted instead. If KeyColumn ends with _ID, NULL will be inserted. A SQL query is created with this values: SELECT AD_User.AD_User_ID, NULL, AD_User.Value || '-' || AD_User.Name, AD_User.IsActive FROM AD_User WHERE EXISTS (SELECT * FROM C_BPartner bp WHERE AD_User.C_BPartner_ID=bp.C_BPartner_ID AND bp.IsSalesRep='Y') ORDER BY 3 Parameters with a reference=Table must be defined as reference with the attributes Table Name, Key Column, Displayname, SQL-Constrain. An instance of MLookupInfo is created with values (TABLENAME, KEYCOLUMN, etc) and SQL. Result of this SQL would be (shortened)
AD_USER_ID NULL AD_USER.VALUE||'-'||AD_USER.NAME 1000000 -VidesAdmin 1000001 -VidesUser 1000808 05-TORRES 1000311 15-TOLENTINO 101 gardenad-GardenAdmin 102 gardenusr-GardenUser 1000002 labvides-LabVIDESAdmin 1000003 labvides-LabVIDESUser 1000004 lsaravia-LSaravia 1001142 wbeltran-W.BELTRAN etc. ISACTIVE Y Y Y Y Y Y Y Y Y Y

MLookupFactory.getLookup_TableDir() org.compiere.model At TableDirect the field muss be named TabellenName_ID! Otherwise it will result in an error. Displaying of the columns, that are marked as Identifier . getLookup_List() Depending on the selected field in Reference Key a list is shown here. An empty list is shown when there is no value in the field Reference Key.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 67

Developers Guide

The ADempiere Bazaar

Parameter analysis at report call


Parameters are analyzed, after they were typed in. Parameter are saved with (org.compiere.apps) processParameterPanel.saveParameters() in table AD_PInstancePara. Depending on the data type the parameters are saved in different columns. The parameters are read from the table AD_PInstancePara during the execution of the report . To achieve this, the method (org.compiere.model) MQuery.get() is called in (org.compiere.print) ReportEngine.get() Then it goes on as described in paragraph Printing

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 68

Developers Guide

The ADempiere Bazaar

Callouts
From Adempiere-Wiki + own research: What is or what mean "callout"? A Callout is a java method that is executed after the focus has changeg away from a field and the field's Value is changed?. Callout is a java method which is executed when a field in Adempiere window is modified. A callout class (extends CalloutEngine) groups different methods that are called when the column is changed using the UI. For a column (see AD_Column.Callout database column and Table and Column tab), you may specify a list of fully qualified methods (separated by ";"). Callouts are not for data validation - use dynamic validation (AD) instead Callouts can read the field or other fields. It is used for data entry consequences like calculate totals that need direct feedback at the GUI. Callouts are deployed in the package org.compiere.model, as well as CalloutInvoice and CalloutEngine If you do calculations in callouts you have to repeat them in the related PO classes (beforeSave()) to allow the acces via a different UI like HTML interface. Callouts in AD System Admin Window Table&Column, Tab Column Field Callout contains method, e.g. the contents of Table C_Order, Column Target Document Type, Field Callout is org.compiere.model.CalloutOrder.docType Callouts in Code Callout-Hierarchy in an example public class CalloutOrder extends CalloutEngine Package: org.compiere.model public class CalloutEngine implements Callout The implemented method start() parses the text and invokes the Method with parameters : (String) method.invoke(this, args). start() is called by ???? Method (e.g. docType)is called and executed. How it proceeds to call the specific callout I can not explain. public interface Callout

Callout signature Every callout has the same signature. public String my_callout_method (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object Value) Example in class CalloutOrder: public String docType (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object Value) Properties ctx Used in cases like MPriceList.getStandardPrecision(ctx, M_PriceList_ID) Env.setContext(ctx, WindowNo, "OrderType", DocSubTypeSO) MWorkflow.get (ctx, AD_Workflow_ID) int WindowNo Used mostly with org.compiere.util.Env class: Page 69

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Developers Guide

The ADempiere Bazaar Env.setContext(ctx, WindowNo, "OrderType", DocSubTypeSO) Env.getContextAsInt(ctx, WindowNo, "C_BankAccount_ID") Env.getContext(ctx, WindowNo, "DiscountSchema") Env.getContextAsDate(ctx, WindowNo, "PayDate") GridTab mTab It represents all columns within a record. Used in cases like (BigDecimal)mTab.getValue("QtyEntered") mTab.setValue ("M_Product_ID", new Integer (M_Product_ID)) mTab.setValue("DateAcct", Value) GridField mField The methods of the Grid Field Model class GridField can be used (getAD_Tab_ID(), getValue(), isDisplayed(), getAD_Column_ID() etc.). Example: String colName = mField.getColumnName(); Object Value Value represents the Value of the field and has to be cast to the class required. It depends on the data type of the underlying field. Examples: ((Integer)Value).intValue() (Timestamp)Value (BigDecimal)Value

Functionality using getValue() and setValue() to interpret and change logic. To avoid loops (not always used): at the beginning of a callout: setCalloutActive(true); at the end of a callout: setCalloutActive(false)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 70

Developers Guide

The ADempiere Bazaar

Validation of context values


Configuration in AD Display Window, Tab&Field of a windows Any tab, field Display Logic contains commands @IsEmployee@=N 1=2 // wird also nie angezeigt @$Element_U2@=Y @processed@=Y & @#ShowAcct@=Y @IsCustomer@='Y' etc Read Only Table&Column of a table Any column, field Dread Only Logic contains commands like @OrderType@='WP' @IsDropShip@=Y @AD_OrgBP_ID@!0 @ProductType@=R | @ProductType@=E | @ProductType@=O @CostingMethod@!x & @CostingMethod@!S // was is this? (in MCost, column Current Cost Price) etc. Mandatory Table&Column of a table Checkbox Mandatory Code I APanel: initPanel() calls m_curTab.getTableModel().setChanged(false) m_cur_tab is the current tab at initialization (initPanel() is called by initWorkbench() in AWindow ) Initializes panel. public class GridTab implements DataStatusListener, Evaluatee, Serializable query() and getTableModel() call initTab(). initTab() in the class GridTab calls loadTab() GridTab: protected boolean loadTab() calls loadFields() does order by GridTab: private boolean loadFields() Gets all fields separated as Standard fields and content fields ( Standard fields like Created, CreatedBy etc.) Calls list of the column names this field depends on: field.getDependentOn() public class GridField implements Serializable, Evaluatee GridField: public ArrayList<String> getDependentOn(): { : Evaluator.parseDepends(list, m_vo.DisplayLogic); Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 71

Developers Guide

The ADempiere Bazaar

Evaluator.parseDepends(list, m_vo.ReadOnlyLogic); Evaluator.parseDepends(list, m_vo.MandatoryLogic); : Evaluator.parseDepends(list, m_lookup.getValidation()) : } Evaluator public static void parseDepends (ArrayList<String> list, String parseString) Parses the corresponding string: everything between @. Code II Evaluate-class evaluateLogic() is also used in methods of GridTab ( isReadOnly() ) and Grid Field ( isMandatory() ). These calls are often done within the program. And analyse Code III Evaluate class There is also isAllVaraiblesDefined() in Evaluate

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 72

Developers Guide

The ADempiere Bazaar

Accounting processor
Comments by Karsten Thiemann in the forum: The Accounting processor works with all document tables (all documents that have a post button) and creates the entries of Fact_Acct. See Acctprocessor.java for details. Especially the postSession() method. The posting is done by the Doc_* classes (Line 119 String error = doc.post(false, false);) Class Doc package org.compiere.acct public abstract class Doc Classes with accounting logic as Doc_GLJournal, Doc_Cash, Doc_Bank, Doc_Invoice, Doc_Inventory, Doc_InOut, Doc_Order, Doc_Payment, Doc_Requisition inherit from Doc. this Accounting logic is implemented in these classes. Each BO, which inherites from PO (as the Bos discussed above), has a reference to a Doc instance, which is the document of the business object: private Doc m_doc, with its access methods public Doc getDoc() public void setDoc(Doc doc) example: public class Doc_GLJournal extends Doc Some methods as loadDocumentDetails(), createFacts(), getBalance() must be overwritten with their own logic by the subclasses. Properties All IDs of the Table, which process documents (postings, documents) public static int[] documentsTableID Now: C_Invoice, C_Allocation, C_Cash, C_BankStatement, C_Order, C_Payment, M_InOut, M_Inventory, M_Movement, M_Production, GL_Journal, M_MatchInv, M_MatchPO, C_ProjectIssue, M_Requisition All names of tables which process documents public static String[] documentsTableName Constants for document types C_Invoice: ARI, ARC, ARF, API, APC e.g. for Accounts Payable Invoices public static final String DOCTYPE_APInvoice = "API"; C_Payment: ARP, APP C_Order: SOO, POO Transaction: MMI, MMM, MMS, MMR C_BankStatement: CMB C_Cash: CMC C_Allocation: CMA GL_Journal: GLJ and so on Constants for posting state Notposted: N Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 73

Developers Guide

The ADempiere Bazaar

e.g.: public static final String STATUS_NotPosted = "N"; NotBalanced b NotConvertible c PeriodClosed p InvalidAccount i PostPrepared y Posted Y Error E Account Type-Constants Invoice Charge e.g. public static final int ACCTTYPE_Charge = 0; Invoice - AR Invoice - AP AP Service AR Service etc. Other properties Accounting Schema private MAcctSchema[]m_ass = null; Properties private Properties m_ctx = null; document BO, derived from PO protected PO p_po = null; Doc Lines protected DocLine[] p_lines; Debit and Credit is accounted here Facts private ArrayList<Fact> m_fact = null; Furthermore Document type, -state, -No, description, GL-Category, period, accounting date, document date, business partner, bank account, currency Methods public final String post(boolean force, boolean repost) I did not find an other callers except postImmediate() (Comment of the author). Check validity of document state Checks whether accounting schema an BO refers to the same Client. Locks the data set. loadDocumentDetails() is called (is scaffolded by each Doc class with its own logic) Example Doc_Invoice: MInvoice is instantiated document date is calculated Quantities are calculated getGrandTotal() getTotalLines() getChargetAmt() Steuern ermittelt Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 74

Developers Guide

The ADempiere Bazaar

Invoice lines loaded: Using loadLines() p_lines and quantity is set (docLine.setAmount() ). if repost==true, then deleteAcct() is called, while the table and record_id entry is deleted from Fact_Acct-table using a SQL query m_fact is filled with data from accounting schema: postLogic()is called for each accounting schema, where createFacts() is called by the subclasses and m_fact is fillled. The State of postings is set too. The BO gets its document using setDoc(this) BO is validated postCommit (String status) , where Facts&Receipts are saved A MNote is instantiated + filled with data as DocumentNo, booking date, quantity, state, periode open?, balanced?. public static String postImmediate (MAcctSchema[] ass, int AD_Table_ID, int Record_ID, boolean force, String trxName) calls post(force, true) it is called e.g. by DocumentEngine: postIt(). public ArrayList<Fact> createFacts(MAcctSchema as) Responsible for the accounting logic overwritten by the accounting classes as Doc_Invoice: Sets the Facts (accounting logic) for ARI, ARC, ARF, API, APC in a huge method fact.createLine() defines a line with debit and credit m_fact is filled in method post(). public BigDecimal getBalance()

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 75

Developers Guide

The ADempiere Bazaar

Class Fact
package org.compiere.acct public final class Fact In the end there is a class Balance Properties private Doc m_doc = null; private MacctSchema m_acctSchema = null; private ArrayList<FactLine> m_lines = new ArrayList<FactLine>() Lines in Fact_Act Methods Constructor public Fact (Doc document, MAcctSchema acctSchema, String defaultPostingType) properties are set public FactLine creatLine(DocLine docLine, MAccount account, int C_Currency_ID, BigDecimal debitAmt, BigDecimal creditAmt) Defines an instance of FactLine. More createLine-methods public FactLine[] getLines() FactLines are returned as array public boolean save (String trxName) All lines are saved. etc.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 76

Developers Guide

The ADempiere Bazaar

Class FactLine
package org.compiere.acct public final class FactLine extends X_Fact_Acct public class X_Fact_Acct extends PO FactLine has persistancy too Realizes functionality of table Fact_Acct Is often used by createFacts in the accounting classes

Properties private Maccount m_acct = null private MacctSchema m_acctSchema = null private Doc m_doc = null; Document Header private DocLine m_docLine = null; Methods public void setDocumentInfo(Doc doc, DocLine docLine) Client. Date Acct, Period, Tax, Product, Quantity, BP, Project, Campain, Activity, usw. public void setLocationFromBPartner (int C_BPartner_Location_ID, boolean isFrom) public BigDecimal getSourceBalance() The line public BigDecimal getAcctBalance() Accounted Balance beforeSave() because of PO createRevenueRecognition() Called by FactLine.save() public boolean updateReverseLine (int AD_Table_ID, int Record_ID, int Line_ID, BigDecimal multiplier) Caller: Doc_MatchInv etc.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 77

Developers Guide

The ADempiere Bazaar

DocTypes and DocBaseTypes


DocBase Types are defined in AD : There is an AD reference called C_DocType DocBaseType as List Validation (=Combo Box) with the following parameters DocBase Type Search Key AP Credit Memo APC AP Invoice API AP Payment APP AR Credit Memo ARC AR Invoice ARI GL Journal GLJ Material Movement MMM Material Receipt MMR etc. (at all 23) DocTypes All accounting actions are processed with the Document Type. All possible document types in accounting are defined at application level. Additionally to DocBase Type there can be defined in Document Type the following: Print Format (selectively), document count, number of copies etc. Example in an application (Spanish): DocType DocBase Type CXP Nota de Crdito AP Credit Memo CxP Retencin de IVA AP Credit Memo CxP Crdito Fiscal AP Invoice CxP Factura AP Invoice CxC Crdito Fiscal AR Invoice Return Material Sales Order CxP Pedido Purchase Order etc. (at all 44 in in the application)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 78

Developers Guide

The ADempiere Bazaar

Application Dictionary: Financial Report


A window appears clicking to Adempiere-Menue/Performance Analysys/Financial Reporting/Financial Report. What is this button for and where is the executing place in Adempiere? Answer Login as System Administrator Application Dictionary/Menue Select path to Financial Reporting window Zoom (right mouse button) into the window of the report (has content Financial Report) Select feld Create Report (which is defined as button) Zoom at the column processing-process Now The column processing of the table PA_Report_Financial Report appears. In the column process FinReport is set. In Application Dictionary/Report & process is actually FinReport the content of the column Search Key of the report Create Report (column Description: Create Financial Report). Column Classname of report Create Report is set to org.compiere.report.FinReport. Starting Eclipse/Netbeans, file FinReport.java, which contains class FinReport, can be found at /adempiere_trunk/base/src/org/compiere/report. =>This class is executed. There are database activities. column ReportView of reports Create Report has an entry T_Report. Zomming the T_Report leads to Temporary Reporting Table, which has a predefined number of columns (29) with their format. The report uses them and fills them the corresponding value into it. I assume that other reports use T_Report(Author).

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 79

Developers Guide

The ADempiere Bazaar

Interaction of Application Dictionary-Application-Code using Landed Costs as an example


Functionality Application Dictionary: Landed Costs and Java method, that implements it. Log in as system adminiastrator Window Menu/Requisition-to-invoice/Invoice (Vendor), Tab Landed Costs: the method can be selected here and the distribution can be started with the button Distributed Costs. Which class is triggered? Log in as System Administrator, open Menu-Window. Goto window Landed Costs (Vendor). Tab Landed Costs Field name Distribute Costs Zoom into Column (the column name is processing). This will lead to the table definition of C_LANDED_COST. There is the column Processing defined as a button, which calls the process C_Landed_Cost_Distribution. Zooming into process C_Landed_Cost_Distribution leads to its definition: field Classname is org.compiere.process.LandedCostDistribute. Application: Perparation of calculation of Landed Costs In the invoice a business process must be set, as well as Company Agent, etc. Creating some invoice lines with product/charge, quantity and price in tab invoice lines In Tab Landed Costs Cost Distribution (value, quantity, etc.), Cost Element (Transport, etc.) and material receipt are set, if necessary with its line. Saving: table C_LandedCost contains the data. There can be multiple Landed Costs defined for eachinvoice line. Tab Landed Cost Allocation must be empty; It is programmatically filled after calculation. Activation of Button Distribute Costs: Landed Costs of an invoice line are triggered by the application. So if someone likes to calculate a whole invoice with multiple invoice lines, this must be done manually for each line Code processCtl:Call of processUtil.startJavaprocess() processUtil.startJavaprocess(): Call of process.startprocess() LandedCostDistribute (Svrprocess): startprocess() calls process(). LandedCostDistribute (Svrprocess): process() calls prepare() and doIt(). LandedCostDistribute (Svrprocess): doIt() creates an instance of Landed Cost and calls m_lc.allocateCosts(). As this is a doIt()-method, this must be a AD-process that is called somewhere. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 80

Developers Guide

The ADempiere Bazaar

Log entry as: LandedCostDistribute.doIt: MLandedCost[1000000,CostDistribution=Q,M_CostElement_ID=1000006,M_InOut_I D=1000039] MLandedCost: allocateCosts(): calls invoiceLine.allocateLandedCosts(). MInvoiceLine: allocateLandedCosts() does: All defined Landed Costs for the invoice line are read from table C_LandedCost and instantiated as MLandedCost array. All entries of the table C_LandedCostAllocation of corresponding invoice line are deleted, if there are any. it is distinguished between single and multiple lines One single line: The shipmentand a list of the shipment's lines is set using the calculated Landed Cost. The complete sum is calculated Etc..

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 81

Developers Guide

The ADempiere Bazaar

Price lists
Each product is part of a Product Category. Each product can have multiple Price List versions. They differ in in List Price, Standard Price and Limit Price. There is a Price List Schema (table: M_DiscountSchema) at Material Management/Material Management Rules. For each Price List Schema fields Valid From, Discount Type and the button Renumber can be seleted here; additionally multiple conditions for each Price List schema can be defined as schema line (table: M_DiscountSchemaLine). For each condition can be defined with which BO it is valid: Business Partner, Product Category or Product. Schema lines have a sequence(number) which defines the order to be processed: starting with the small an ending with the big sequence numbers. Steps of 10 are defined as default. Conditions are mostly prices and discounts (List Price Discount in %, List Price Min Margin, List Price Max Margin etc.). There is a Price List (table: M_PriceList) in Material Management/Material Management Rules. Price lists and its versions can be defined here (table: M_PriceListVersion). It is more important that product prices can be set in register version for each price list. Price List Schema (see above) and one or none aktive Price List Version (in the programm mistakengly called Base Price List!!) are needed for this. In the combo box all active Price List Versions are displayed for selection. Finally a price list can be defined based on a Price List Schema and Price List Version. Click button Create Price List for this. This can also be followed using the database: The Feld M_PRICELIST_VERSION_BASE_ID, that refers to a dataset of table M_PRICELIST_VERSION, is in the table M_PRICELIST_VERSION . Paradoxically this field is defined as foreign key. Summarized:

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 82

Developers Guide

The ADempiere Bazaar


M_PriceList

M_DiscountSchemaLine

M_DiscountSchema_ID M_PriceListVersion

M_DiscountSchema

M_Pricelist_ID M_DiscountSchema_ID

Ein Price List Schema (M_DiscounSchema) hat mehrere Schema Lines (M_DiscounSchemaLine). Eine Price List hat mehere Price List Versions. Die Price List Version Verweist auf ein Price List Schema. Wenn man also aus eine Price List Version die Preise kalkulieren will, greift Adempiere auf die im Price List Schema definierten Schema Lines zurck

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 83

Developers Guide

The ADempiere Bazaar

Price calculation
M_ProductCategory M_PriceList

M_PriceListVersion

M_ProductPrice M_Product M_PriceListVersion_ID M_Product_ID

M_PriceList_ID

M_ProductCategory_ID

Ein Product gehoert zu einer Product Category. Eine Price List hat mehere Price List Versions. Ein Product und eine Price List Version verweisen auf einen Satz von Product Price. Dazu kommen in M_ProductPrice die Spalten PriceList, PriceStd, PriceLimit, die fr Listenpreis, Standardpreis und Mindestpreis stehen. In M_ProductPrice wird das Ergebnis der Preiskalkulation festgehalten. Im Fenster Price List, Register Product Price werden die Werte fr Listenpreis, Standardpreis und Mindestpreis angezeigt.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 84

Developers Guide

The ADempiere Bazaar

Payment Term

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 85

Developers Guide

The ADempiere Bazaar

Views RV_C_INVOICE, RV_OPENITEM


RV_OPENITEM is a view, that is called when Menue/Open Items/Open Items is selected. View RV_C_INVOICE is used in RV_OPENITEM ; so it is inspected first. 1.- In RV_C_INVOICE the table C_INVOICE is conected to the tables C_DocType, C_BPartner, C_BPartner_Location and C_Location using an inner join. The result contains all lines that have reference to this table . So there can not be more lines as in C_INVOICE. Semantics: probably just completed invoices are processed. Almost all columns of the view RV_C_INVOICE (52 columns) derive from table C_INVOICE (60 columns) excepted C_CountryID, C_Region_ID, Postal and City, which derive from table C_Location. There is a column DocStatus in the table C_INVOICE; its values consist of 2 letters. CO -> completed, DR -> Drafted. See other sections in this documentation for a complete list. Columns ChargeAmt, Totallines and GrandTotal of the table C_INVOICE can switch to negative values while creating the view, if Multiplier gets the value-1, when the third letter of the column is DocBaseType equals C. DocBaseType is a column of the table , and has values like: CMC (Cash Journal), APC (Vendor Credit Memo), ARC (Credit Memo), MMR (Vendor Delivery), POO (purchase Order), SOO (Order Confirmation, Proposal, Quotation, etc). CMC , APC and ARC are the only values, that have a C in third position. It seems that only these document types are used to create the view RV_C_INVOICE. The function charAt() read the n-th letter in a string (the code is simple: RETURN SUBSTR(p_string, p_pos, 1) ).

2.- view RV_C_INVOICE is used in View RV_OPENITEM. This is a bit more complicated. view RV_C_INVOICE consists of a union Remember: data type of the selected table columns must be equal in both tables during the union. A simple union returns non-repeating lines, which means same lines are filtered. A UNION ALL returns repeating lines. Because of this the column names of the second part of the UNION and and their order are Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 86

Developers Guide equal to the first of this part

The ADempiere Bazaar

First set fo union: First an inner join between the View RV_C_INVOICE and the table C_PaymentTerm is created. Explaination: A Payment Term can have multiple schedules. The Payment Term is calculated in register Invoice during the creation of an invoice. The values for DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt are calculated from selected Payment Term during this first join. Calcuations are made using funtions as paymentTermDueDate(), which calls the method org.compiere.sqlj.PaymentTerm.dueDate(i.C_PaymentTerm_ID, i.DateInvoiced). This method calculates the days to maturity using the invoiced date and the payment conditions. Other Oracle functions as addDays(i.DateInvoiced,p.DiscountDays) are used. DiscountDays from the table C_PaymentTerm is fetched here. This function is solved as following: RETURN TRUNC(p_date) + p_days. Some columns of view RV_OPENITEM are defined new in the view: DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt. Their values are calculated using functions. Calculation of these columns are the difference to the 2. join. Almost all other columns columns are from the view RV_C_INVOICE. As almost all columns of the view RV_C_INVOICE derive from table C_INVOICE, is can be said that view RV_OPENITEM is created using the table C_INVOICE adding some calculated fields. where-condition defines that amog other things that lines are selected where payment has not been done (i.IsPayScheduleValid<>'Y') and document should not have state Draft (i.DocStatus<>'DR'). 2. set of union An inner join is done between the View RV_C_INVOICE and the table C_InvoicePaySchedule. Info: table C_InvoicePaySchedule is displayed inwindow Invoice (Customer), displayed as 4. tab (named Payment Schedule). A Payment Schedule and some parameter as maturity date and discount date are selected here. Payment Schedule depends on Payment Term, which is selected in register Invoice. Window Invoice (Customer) is executed in Menue/Quote-to-Invoice/Sales Invoices/Invoice (Customer). The same columns as during the first join are used; just the selection ofcolumns as DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt is different. where-condition has an additionally component: wheter payschedule is valid.

So what does this view? It selects the mature invoices for the set of invoices and calculates the current day and the discounts for every invoice. I dont know, why this value are calculated twice (once via Payment Term and once via Schedules) (Author).

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 87

Developers Guide

The ADempiere Bazaar

Interestingly this view is saved into temporary table T_Aging (i think it is because of performance reasons). The dictionary results as following: The Report&Process RV_T_Aging uses Report View T_Aging, RV_T_Aging uses org.compiere.process.Aging as classname, where amongst other view RV_OpenItem is called. T_Aging defined as table (and not as view like in other reports). The relations between the table are shown in the diagram:

Comments: As example the table C_InvoicePaySchedule and the register definition of Payment Schedule in dictionary it can be seen that not all columns, which are defined in the table, are accessable via the dictionary. There are 17 columns in the table definition of C_InvoicePaySchedule. In Application Dictonary just 13 columns can be selected for register definition of Payment Schedule. The columns created, createdBy, updated and updatedBy are not accessable via the dictionary.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 88

Developers Guide

The ADempiere Bazaar

Explanation of the warehouse structure in the DB


warehouse is found in Adempiere in Material Management/Material Management Rules/Warehouse and Locator. Here the warehouse, it(s) locator(s) and the storage per locator can be seen. In the table m_warehouse the warehouses are saved. Each warehouse can have multiple Locators. The are registered in the table m_locator. Locators have Search value (for fast lookup). A format-proposal for the value field is for 3dimensional warehouses xx-yy-zz, where 08-13-06 defines the 8. Aisle, 13.Din and dort 6. level. Aisle oder "X" Bin oder "Y" Level oder "Z" A lot of locators for any kind of 3-dimensional warehouse can be modeled this way. Each Locator and Product be addressed to a storage. It is best practice to select this format for Search Key: warehouse xx-yy-zz. In reports it would be displayed as: Movement from central warehouse 23-44-3 to distribution warehouse 21-44-4. Storages are saved in the table m_storage and have on hand quantity, reserverd quantity, ordered quantity, last inventory count, etc as parameters. It is allowed to use different storages for each product and locator, so that a 3-dimensional storage is not needed. This is useful because e.g. with charges the same product can have different charge numbers and warranty dates.

Eentries of m_storage can not be deleted (delete from m_strorage where ad_client_id=1000001 is useless too). If there is a wrong m_storage-entry, ad_client_id, m_product or m_locator must be changed. Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al Page 89

Developers Guide

The ADempiere Bazaar

SQL query to find the relation locator-warehouse select w.m_warehouse_id, w.name, l.m_locator_id FROM m_warehouse w INNER JOIN m_locator l ON (w.m_warehouse_ID=l.m_warehouse_ID) where w.ad_client_id=xxxxx Pay attention that ad_client_id is correct.

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 90

Developers Guide

The ADempiere Bazaar

Attribute Set Instances


This is where description of an attributsetinstance is defined. MAttributeSetInstance.setDescription() The descriptor is composed of the attributes Amongst other MAttributeSet.getLotCharStart() and MAttributeSet.getLotCharEnd() are called. values can be defined in window Attribute Set, field Lot Char Start Overwrite and lot Char Start Overwrite. Blanks are not processed. Additional table relations: 1 n MAttributesetInstance ------> MStorage (Lot, (m_attributesetinstance_id Serno, qtyonhand, Guaranteedate) qtyreserved, qtyordered) So there can be different charges(MattributesetInstances) in a locator for the same product. SQL query to get locator, product, quantity and description: select loc.value, prd.value, st.QTYONHAND, asi.m_attributesetinstance_id, asi.description from m_storage st join m_attributesetinstance asi on (st.M_ATTRIBUTESETINSTANCE_ID=asi.M_ATTRIBUTESETINSTANCE_ID) join m_locator loc on (st.M_LOCATOR_ID=loc.M_LOCATOR_ID) join m_product prd on (st.M_PRODUCT_ID=prd.M_PRODUCT_ID) order by asi.lot, asi.guaranteedate, prd.value; Further relationships: 1 n MAttributesetInstance ------> MOrderline (Lot, (m_attributesetinstance_id Serno, qtyordered, Guaranteedate) qtyreserved, qtydelivered m_product_id)

Copyright 2008 Mario Calderon, Victor Perez, Norbert Wessel et al

Page 91

Das könnte Ihnen auch gefallen