From ADempiere ERP Wiki
Jump to navigationJump to search
Web Mistress of
Name This user real name is : Jireh Arciaga
Sourceforge logo.png This user has a Sourceforge account.
Yahoo mail Yahoo email :
  • So pay attention to how you listen. For to the one who has something, more will be given. However, from the one who doesn't have, even what he thinks he has will be taken away from him. (Luke 8:18)'
  • Whatever you do, work at it with all your heart, as working for the Lord, not for men, (Col 3:23)
  • Do your own work well, and then you will have something to be proud of. But don't compare yourself with others. We each must carry our own load.(Galatians 6:4-5)
  • Do not worry about anything, but pray and ask God for everything you need, always giving thanks. And God’s peace, which is so great we cannot understand it, will keep your hearts and minds in Christ Jesus.(Philippians 4:6-7 (NCV))

Want to know me better?[edit]

First, as you can see, I'm a Christian, a God fearing person. My Lord and Savior is Jesus Christ the son of God. If you have something against Christians, please don't start asking any question/s that will lead to an argument or something like that, it's not healthy :). If you have questions about the website or anything that you think I can be of help, then I would gladly answer it ;D. You can easily approach me through yahoo email/messenger, as long as you introduce yourself :)

Other things I do...[edit]

  • Programming
    • PHP Language
    • PHP Framework: Symfony
    • Some basics of VB.Net
    • Some basics of JAVA EE (servlets and jsp)
  • Databases
    • Once experimented on IBM DB2 (college days)
    • MYSQL
    • Studying more on Postgres Plus and Postgresql (currently)
  • Drupal (currently CMS of
  • I use Adobe Photoshop most of the time
  • ERP Systems
    • MFG/PRO (older version, mmm.. version of 8 years ago)
    • Adempiere (planning on introducing to the higher management)
    • Compiere
    • OpenBravo (Did some minor Studies and Analysis)
    • OpenTaps (Did some minor Studies and Analysis)
  • Software Quality/Assurance
  • Macromedia Flash Animation

My Stress Relievers[edit]

(it might work on you too ;D)

  • I Listen to Music
    • Pop / Slow Pop (most of the times)
    • Alternative
    • Punk / Rock (when I'm trying to forget something or trying not to think of something)
  • Drawing Anime or anything I feel like drawing
  • or I watch
    • Anime / Japanese Drama
    • House (latest season)
    • Grey's Anatomy (latest season)
    • CSI NY and Miami (when I have time)
    • Ally McBeal (sometimes, when I feel like watching it)
    • Gossip Girl (latest season)
    • movies but I hate horror / suspense movies
  • I go somewhere where I can spend some quite time alone :)
  • Do anything I want (as long as I'm not doing my work)
  • or I eat popcorns
  • or I play
    • (Desktop) Tower of Defense (Bloons or Kongregate)
    • Facebook: Pet society (I like designing the house of my pet)

Some of My ADempiere Documentations[edit]

These are my own references based on some tutorials found also in this wiki. It's just that, for me, diagrams are much better than texts :D... If there are corrections, please contact me so I can change it immediately... :)

Adempiere 342s Production Flow[edit]


Adempiere 342s Purchase Order (PO) Flow[edit]

Purchase order.jpeg

Adempiere Sales Order (SO) Map[edit]

Why sales order map? Well, when I was starting I'm having a hard remembering what sales order and how it produce it depending on its document type and other rules. I made this so I can quickly see what it generates and I don't like scanning articles just to search for one answer. Just sharing this for those who need it.. :)

GERMAN VERSION of Sales Order Map is available thanks to Thomas Kreser of

Direct Image Link: Blog:

Sales Order MAP.png

Inventory Management Flow[edit]

Well, here it goes, a basic flowchart of inventory management using ADempiere's warehouse and Locators. Based this from ADempiereReleaseManual.pdf[1] by Red1. (NOTE: Basic flow only) Inventory Management.png

ADempiere Transaction Flow with Accounting Postings[edit]

Sales and Purchase to Bank Statement process flow with accounting postings. SO and PO to Bank Statement Postings.jpg

Adempiere Libero: BOM[edit]

Setting up BOM

*First, create all the product you'll be using for BOM.

Example: Orange Juice

You have to have the following products: Orange Juice, Orange, Water, and Packaging

You've noticed that the BOM checkbox in ADempiere Libero is disabled, while in Standard ADempeire, it's not, right? It was disabled because the system will be checking it for your after you setup your BOM under Bill of Materials and Formula. (Manufacturing Management->Engineering Management->Bill of Materials & Formulas->Bill of Materials & Formula)

Put inventory in your product. You can put inventory by Purchasing, by MO (but you have to have BOM for MO, so let's skip that) or by setting your product type to resource or service. Example: Water as resource :)

*Next, create your BOM in Bill of Materials & Formula window.

  1. Choose the product your going to produce (Example: Orange Juice)
  2. Fill out the other needed fields and Click Save
  3. Click New under Components of the BOM & Formula
  4. Put all the products you need in creating your product mentioned above (Example: Orange, Water, and Packaging)
  5. Click Save

Now, go back to the product window and look at the product Orange Juice. You will notice that checkbox BOM is now checked and it has now a verify button under it. Basically, that's how you create BOM for Adempiere Libero. :)

Understanding Libero[edit]

These are the documents that I've used that helped me understand Libero Manufacturing. Thanks to Victor,Bcahya,Red1, and other people in the community for being patient with me. Thanks also to these documents... :)

ADempiere Libero: Manufacturing[edit]

I was able to understand some of Libero's function, because of this tutorial [2] by User:Vpj-cd. This might not be the latest but it explains the basic quite well, and if you'll think more you'll notice that the difference wasn't that big. Thanks to Victor for providing this.

ADempiere Libero: Manufacturing Sample Data[edit]

If you are not sure whether you're using the right data for the function you're testing, here's a pdf that will help you understand more of what and where you should put the data that you're processing. Here's the link [3] thanks again to User:Vpj-cd for sharing this really helpful documentation. It sure helped me a lot! :)


These are my notes... meaning, these are the things I wanted to take not since I haven't read this in any documentation... :)

In setting up a new client in Adempiere:[edit]

When I used the name TestClient I couldn't add a Model Class Validator in Client's window, but if I'm going to use a different name like MyCompany (as long as it doesn't start with test), then I could put a class validator. :D

Using Physical Inventory:[edit]

You use physical inventory if there's a discrepancy between system's inventory and warehouse inventory. Usually, if you check your warehouse (some monthly and some yearly, it depends on the company) and found out that some stuff were stolen/not good anymore, you'll use Physical Inventory to change the number of inventory in the system...but, you have to setup product cost first...

  1. go to Product Cost
  2. enter name of the product..
  3. go to product cost tab
  4. add a product cost like standard costing (sometimes it already has so you don't have to make one, just change the value if it's 0 to 1 or the cost of your product :) )...
  5. fill the current cost price
  6. then try again the to complete the Physical inventory where the product you named is exist.

I got this from this forum: The reason why you have to add cost when changing Physical Inventory is for accounting purposes. They have to have record of those stolen or whatever-happened-to-that product in their record.

Getting the Current Log in User:[edit]

Go to the column in Table&Column window; make sure you choose the right table and column. Example, in SalesRep_ID @ Purchase Order window

  1. Log in as System Admin
  2. Go to Table and Column
  3. Look for C_Order table
  4. Go to Column tab and look for SalesRep_ID
  5. Set Reference to Table Direct
  6. Set Dynamic Validation to AD_User - Internal
  7. Go to Default Logic field
  8. Add @#AD_User_ID@
  9. Log out then log in again as the user (not SuperUser)

If it did not work, check if the user you used is a sales representative. That is why it will not work if you are going to use SuperUser as your log in user.

Setting up Purchase Product properly[edit]

  1. Go to Product window
  2. Fill up the Product Tab
  3. Fill the Purchase Tab (or else you won't be able to convert Requisitions to PO)
  4. Fill up the Price Tab (this is always needed unless your product is Resource)

Got this from this forum: [4]

Could not save changes - data was changed after query...[edit]

I encountered this problem when I added an additional checkbox, so if you added one and has been encountering this error, here's what you're missing... Just make sure you put a default value Y/N, depending on your need.

Got it from this forum:

Linking Tabs[edit]

I'll assume that you have 2 working tabs for this one and you wanted to link the 1st tab to the second tab. Just fill up the Tab Level under Window, Tab and Field->Tab you can see that it's one of the field there. In Sales Order the Order tab is Tab Level 1 and for its Order Line it's Tab Level 2. Hope this helps! :D

Got this from this topic:

Sales Order & Purchase Order in one Table[edit]

As you know, they both shares one table: C_Order How did they separate Sales Order data to Purchase Order data? It's simple, just go to Windows, Tab and Fields -> Tab Then you'll see the SQL Where field (in SO/PO it's IsSOTrx='Y' or IsSOTrx='N') IsSOTrx = Is Sales Order Transaction

Hope this helps... :) Reference:

New Attribute Set Instance (ASI)[edit]

New ASI was being created during:

- Purchase: Material Receipt
- Production: Order Receipt & Issue

Tracing the newly created ASI in production[edit]

After searching and looking for tables where newly created ASI was being stored after Order Recipt and Issue, we've come up with an sql, hope this helps. (Pls msg me if you think there's a better way ^_^)

SELECT pcc.M_AttributeSetInstance_ID, pcc.M_Locator_ID
FROM PP_Cost_Collector pcc
LEFT JOIN PP_Order ppo ON (pcc.PP_Order_ID=ppo.PP_Order_ID)
WHERE ppo.PP_Order_ID=<PP_Order_ID - just find your way how to get it>
AND pcc.M_Product_ID=ppo.M_Product_ID
AND ppo.M_Product_ID=<M_Product_ID - get the product stated in PP_Order table>
AND pcc.M_AttributeSetInstance_ID <> 0

No Info Column Error[edit]

I often get this error because I often miss two important configuration. First, the Window field in Table&Column. Meaning what window is this table being used. And secondly, the Identifier of column in Table&Column. So if you get this error, make sure you check both details. :)

Restrict/Filter List Validation[edit]

You can filter/restrict some parts using dynamic validation.

(('@field1@'='item1' AND AD_Ref_List.Value IN ('item3', 'item4)) OR ('@field1@'='item2' AND AD_Ref_List.Value IN ('item5', 'item6))

This code was mentioned by Carlos Ruiz in this forum:

Libero Costing Method[edit]

Support standard costing for now. Reference:

Order Policy[edit]


  1. Fix Order Quantity: Establish fix quantity for every order created
  2. Lot for Lot: If you have 5 in demand order, each demand is equivalent to one order
  3. Period Order Quantity: If you have 5 Order Period then it sum all the requirements you need for 5 days. If you need 4 product to be produced each day, you'll be having an order with 20 products to be produced.

Ex: 5days * 4product/day = one planned order

Getting Environmental Variables[edit]

All values that you can see under Tools > Preferences > Context like AD_Org_Name can be called through this line:

String OrgName = Env.getContext(getCtx(), "#AD_Org_Name");

Duplicate Key: Used 3rd Party Software to Import Data[edit]

Always run the Sequence Check whenever you use a third party software that will populate your table or database. If you don't do this you'll receive a duplicate key error. :) Reference:


It's because your BOM Type is Make-To-Order. Meaning, upon ordering, ADempiere will produce, but how can it properly produce if you don't have Product Plan? :)

UOM, UOM, UOM!!![edit]

And I have to learn this in our production test. I'm glad it's still a test (in production environment)

  1. PRODUCT window - UOM : your product's uom. Ex: Orange Juice 250mL
  2. BOM window - UOM: your production's uom. You have to change this if you'll be producing, example, a CARTON of Orange Juice.
  3. UOM Conversion tab - here you'll have to put how your product is converted; Under mL UOM create a conversion to POUND, Let's say 1CTN = 2500mL
  4. SALES ORDER Line tab - UOM of the order. Ex: A customer ordered 50 CTN of Orange Juice, you have to choose CARTON instead of mL. You'll see in Ordered Quantity field the converted 50 CTNs in mL.So you should have 125,000mL (50 * 2500) in PO Quantity of Manufacturing Order window :)

Price and UOM Standard Precision[edit]

If you want to change the decimal places in your PO Line or SO Line Unit Price, then the best way is to change UOM Standard Precision. If your UOM is KG then change KG's standard precision. :)

Sales Order Converted Currency Line[edit]

Located in org.compiere.model.GridTab

Check getTrxInfo()

From: [5]

Callout: Check Cursor in Field[edit]

You can do this if you want to execute a certain code if the cursor is in the specified field.

mField.getColumnName().equals("<column_name>") // replace <column _name> to actual column name

More example? Look into CalloutOrder class in amt method. :)

Manufacturing: Component Check[edit]

I found a very interesting conversation about Component Check :) About No Component Check result... Check it out here: [6]

Non-terminating decimal expansion[edit]

When you divide don't forget to round, just in case it will return an infinite result...

 var1.divide(var2, 2, RoundingMode.HALF_UP)

source website: [7]

Window Converted Currency in SO/PO[edit]

If you wan't to change the way it's being computed, you can find the code under

org.compiere.model.GridTab under getTrxInfo() method.

Thanks to Carlos for this information. forum: [8]

Error: Duplicate Key...m_location_where[edit]

If you have encountered this error (which I believe can appear in other scenarios if I'm not mistaken)

 duplicate key value violates unique constraint "m_location_where"

Try checking the warehouse accessibility. If the user is using a certain warehouse for his/her transaction then it should have an access to that warehouse and not only "Read Only" access. I want to restrict the access in the window and restricting it through role was a mistake. You can try creating the field read only in certain conditions or duplicate the window and make that window with read only access. Hope this helps :)

Cannot Export Report in Excel?[edit]

Yeah, we've been there, it was funny how we got the answer from this forum: 360LTS_POI_library

Just make sure you have apache poi package under your {ADEMPIERE_HOME}/packages/poi/lib/

1) Basically, what you should do is copy the "poi" folder located under adempieretrunk/packages/

2) Paste it in your Adempiere_home/packages folder

3) Run adempiere setup again

4) And it should be working... ;)

if not, we haven't gotten to that point so we still don't know what to do :)

Dynamic Validation and Reference Key (Where clause)[edit]

If you're confused with these 2 fields' functionality, then I hope I can help you understand their use.

1) Reference Key (Table Validation, WHERE clause) - does not accept context variables

2) Dynamic Validation - accepts context variables (example: @AD_Org_ID@)

Maintaining User Role/Table Change Log[edit]

Have you ever wondered if you can track the changes made by the user or a specific role? I did. Then I found out about Maintain Change Log checkbox found in Role window. If you don't want to monitor the changes based on role but only on a specific table, you can go to Table and Column. Look for the table and click Maintain Change Log.

To view the changes, go to Change Audit under System Admin > General Rules > Security

Menu Toolbar: Copy Record[edit]

Have you noticed in the menu toolbar the copy record button? If you don't want some field to be copied to the next record you can just go to System (by logging in as System) then Table and Column. In the column field you'll find the Allow Copy checkbox before Synchronize column. Uncheck that and that field/column won't be copied anymore. :)

Setting value to Account Dialog Organization[edit]

I don't know if what I did is the best way but I for now I need to set a default Organization in one of our client setup every time the account dialog box appears.


Method: setValue()

I've added the filter

    m_value = 1000000; //example Shirt org
    value = 1000000; //example Shirt org

I have no time right now to check but it worked. I'll have my teammates review it later, but if you have a better way to do it, please correct me or update me. Thanks! And I hope for some this will help.

iReport - Good to Know in Passing Parameter(s)[edit]

1) As of version 360LTS.015 @ 2010-06-14 when you pass a Record_ID (example from Fact_Account) and you'll incorporate the report inside a process button of a specific window (example a button for Journal Entry report in Invoice from Supplier window), you cannot make the parameter read only and at the same time Big Decimal. What do I mean by that? If you put a Read Only logic in your Big Decimal parameter the system will return the following error

ProcessError java.lang.String cannot be cast to java.math.BigDecimal

If you want an explanation, I still haven't discovered the reason because I don't have time to dig in to the details anymore, but I hope that in the next build of ADempiere, this will be fixed. Unless I lack something in my window settings. :)

Can not select multiple records in a table? Here is a way...[edit]

You have to create an array list of objects of that certain class.

Example: I want to select all the Movement Lines and I don't want to use try and catch just to be able to SELECT it. SQL: SELECT * FROM M_MovementLine (you can add WHERE Clause if you want, just like any normal SQL)

 public static MMovementLine[] getLines (Properties ctx, Integer M_Movement_ID, Integer M_Product_ID, String trxName)
 		String sql = "";
 		ArrayList<MMovementLine> list = new ArrayList<MMovementLine>();
 			sql="SELECT *" +
 				" FROM M_MovementLine" +
 				" WHERE M_Movement_ID=?";
 				 sql+=" AND M_Product_ID=?";
 		PreparedStatement pstmt = null;
 		ResultSet rs = null;
 			pstmt = DB.prepareStatement (sql, trxName);
 			pstmt.setInt(1, M_Movement_ID);
 			     pstmt.setInt(2, M_Product_ID);
 			rs = pstmt.executeQuery ();
 			while ( ())
 				list.add(new MMovementLine (ctx, rs, trxName)); //this is the code that adds to arraylist
 		catch (SQLException ex)
 			s_log.log(Level.SEVERE, sql, ex);
 			DB.close(rs, pstmt);
 			rs = null; pstmt = null;
 		MMovementLine[] retValue = new MMovementLine[list.size()];
 		return retValue;

I've placed this code under MMovementLine class. Now, calling this ArrayList, I have to use for each loop.

 MMovementLine[] lines = MMovementLine.getLines(getCtx(), MovementID, 0, get_TrxName());
 for(MMovementLine line:lines){
         int x = line.getLine();
         int ID = line.getM_Movement_ID();
         //other codes

TrxName is important so that there will be no changes executed until the whole process is done.

Hope this helps. :)

Creating a Button with Process (Report&Process)[edit]

For some reason in Inventory Move window, the button Create Lines (from certain table) isn't working... I posted a question in the forum but I didn't get any answer. I thought, maybe there really is no process in this button and maybe the people who read my question knew that there really is no process for that, and it's not a bug, since I asked if it's a bug or not.

Anyway, I have to find a way to create a button with process in it. A process that will copy all the details from a specific table and post in Move Line tab.

Btw, if you really want to try this out, you should have a Development Environment,

Scenario: I have to copy the lines from Material Receipt window or M_InOutLine table and insert it too in M_MovementLine.

Table and Column:

  1. DB Column Name: GenerateList
  2. System Element: GenerateList
  3. Name: Generate List
  4. Length: 1
  5. Reference: Button
  6. Process: Create Movement Line List (you can only put process here if you have made the process; for this case, since you haven't made one just leave it blank)

Widow, Tab and Field

  1. Name: Copy MR Lines (since I'll copy the Material Receipt lines)
  2. Description: Generate List
  3. GenerateList_Generate List

Display Length: 22

Report and Process

For those who are curious how I did this, I used InventoryCountCreate class as my basis. :P Disclaimer: This is my first time creating a java process, so if there are some corrections or best coding style that you can recommend to me so I can update this, please don't hesitate, I would love to hear your opinion. :)

Here comes the climax of this note! :D

  1. Search Key: M_Movement Create
  2. Name: Create Movement Line List
  3. Description: Create Movement Line List
  4. Comment/Help: This will create the movement line based on Material receipt selected
  5. Data Access Level: Client+Organization
  6. Classname: org.compiere.process.CopyMRLine (put the location with class name here)

Parameters for this process are:

  1. Material Receipt (M_InOut_ID)
  2. Source Warehouse (M_Locator_ID)
  3. Target Warehouse (M_LocatorTo_ID)

Note: In my material receipt ID, I want it to be read only and has default value, so under Default Logic, I placed this code @M_InOut_ID@ (this will automatically put value in my Material Receipt field.

You need to fill the mandatory fields in Movement line to be able to successfully create the lines and save it. For this, we have to make sure Locator from and to is not null.

Here's the code... Place it under org.compiere.process

package org.compiere.process;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.logging.Level;
import org.compiere.model.MMovement;
import org.compiere.model.MMovementLine;
import org.compiere.util.AdempiereSystemError;
import org.compiere.util.DB;
import org.compiere.util.Env;

public class CopyMRLine extends SvrProcess{
	/** Inventory Movement Parameter	*/
	private int			p_M_Movement_ID = 0;
	/** Material Receipt Parameter			*/
	private int			p_M_InOut_ID = 0;
	/** Inventory Movement				*/
	private MMovement 	m_movement = null;
	/** Locator Parameter				*/
	private int			p_M_Locator_ID = 0;
	/** Locator To Parameter			*/
	private int			p_M_LocatorTo_ID = 0;
	/** Inventory Movement Line			*/
	private MMovementLine 	m_mline = null;

	 *  Prepare - e.g., get Parameters.
	// This is how you get your parameter that you'll be passing from Report&Process Parameter
	protected void prepare()
		ProcessInfoParameter[] para = getParameter();
		for (int i = 0; i < para.length; i++)
			String name = para[i].getParameterName();
			if (para[i].getParameter() == null)
			else if (name.equals("M_InOut_ID"))
				p_M_InOut_ID = para[i].getParameterAsInt();
			else if (name.equals("M_Locator_ID"))
				p_M_Locator_ID = para[i].getParameterAsInt();
			else if (name.equals("M_LocatorTo_ID"))
				p_M_LocatorTo_ID = para[i].getParameterAsInt();
				log.log(Level.SEVERE, "Unknown Parameter: " + name);
		p_M_Movement_ID = getRecord_ID();
	}	//prepare
	 * 	Process
	 *	@return message
	 *	@throws Exception
	protected String doIt () throws Exception
	{"M_Movement_ID=" + p_M_Movement_ID
				+ ", M_Locator_ID=" + p_M_Locator_ID + ", M_LocatorTo_ID=" + p_M_LocatorTo_ID);
			m_movement = new MMovement (getCtx(), p_M_Movement_ID, get_TrxName());
			if (m_movement.get_ID() == 0)
				throw new AdempiereSystemError ("Not found: M_Movement_ID=" + p_M_Movement_ID);
			if (m_movement.isProcessed())
				throw new AdempiereSystemError ("@M_Movement_ID@ @Processed@");
			StringBuffer sql = new StringBuffer(
					"SELECT iol.M_Product_ID, iol.M_Locator_ID, iol.M_AttributeSetInstance_ID, s.QtyOnHand" +
					" FROM M_InOutLine iol" +
					" INNER JOIN M_Storage s ON (s.M_Product_ID=iol.M_Product_ID)" +
					" WHERE iol.AD_Client_ID=?" +
					" AND s.M_Locator_ID = iol.M_Locator_ID" +
					" AND s.M_AttributeSetInstance_ID=iol.M_AttributeSetInstance_ID" +
					" AND iol.IsActive='Y'");
				if (p_M_Locator_ID != 0)
					sql.append(" AND s.M_Locator_ID=?");
				if (p_M_InOut_ID != 0)
					sql.append(" AND iol.M_InOut_ID=?");

				int count = 0;
				PreparedStatement pstmt = null;
					pstmt = DB.prepareStatement (sql.toString(), get_TrxName());
					int index = 1;
					pstmt.setInt (index++, m_movement.getAD_Client_ID());
					if (p_M_Locator_ID != 0)
						pstmt.setInt(index++, p_M_Locator_ID);
					if (p_M_InOut_ID != 0)
						pstmt.setInt(index++, p_M_InOut_ID);
					ResultSet rs = pstmt.executeQuery ();
					while ( ())
						int M_Product_ID = rs.getInt(1);
						int M_Locator_ID = rs.getInt(2);
						int M_AttributeSetInstance_ID = rs.getInt(3);
						BigDecimal MovementQty = rs.getBigDecimal(4);
						if (MovementQty == null)
							MovementQty = Env.ZERO;
						count += createMovementLine (M_Locator_ID, p_M_LocatorTo_ID, M_Product_ID, 
								M_AttributeSetInstance_ID, MovementQty);
					rs.close ();
					pstmt.close ();
					pstmt = null;
				catch (Exception e)
					log.log(Level.SEVERE, sql.toString(), e);
					if (pstmt != null)
						pstmt.close ();
					pstmt = null;
				catch (Exception e)
					pstmt = null;

				return "@M_MovementLine_ID@ - #" + count;
	} //doIt

The code below will be creating a new line in Inventory Move line. What it does is it copies the line in MR Line and put it in Move Line. Just add this under the code from above (but still inside the class).

	 * 	Create/Add to Inventory Move Line
	 *	@param M_Product_ID product
	 *	@param M_Locator_ID locator
	 *	param M_LocatorTo_ID locatorto
	 *	@param M_AttributeSetInstance_ID asi
	 *	@param QtyOnHand qty
	 *	@param M_AttributeSet_ID as
	 *	@return lines added
	private int createMovementLine (int M_Locator_ID, int M_LocatorTo_ID, int M_Product_ID, 
		int M_AttributeSetInstance_ID, BigDecimal MovementQty)
		//	new line
		m_mline = new MMovementLine (m_movement, M_Locator_ID, p_M_LocatorTo_ID, 
			M_Product_ID, M_AttributeSetInstance_ID, MovementQty);	
		if (
			return 1;
		return 0;
	}	//	createMovementLine

Also, you have to have this code added to MMovementLine (org.compiere.model). This is the one who'll be setting the values to Movement Line. (please don't forget to put comments/mark in your added line so it won't get mixed up the the default codes)

public MMovementLine (MMovement movement, 
			int M_Locator_ID, int M_LocatorTo_ID, int M_Product_ID,
			int M_AttributeSetInstance_ID, BigDecimal MovementQty)
			this (movement.getCtx(), 0, movement.get_TrxName());
			if (movement.get_ID() == 0)
				throw new IllegalArgumentException("Header not saved");
			m_parent = movement;
			setM_Movement_ID (movement.getM_Movement_ID());		//	Parent
			setClientOrg (movement.getAD_Client_ID(), movement.getAD_Org_ID());
			setM_Locator_ID (M_Locator_ID);		//	FK
			setM_LocatorTo_ID (M_LocatorTo_ID); // -added by jai
			setM_Product_ID (M_Product_ID);		//	FK
			setM_AttributeSetInstance_ID (M_AttributeSetInstance_ID);
			setMovementQty (MovementQty);
		}	//	MMovementLine

There you go! :) A button with process that copies lines from MR to Inventory Move Line.

Creating COMPLETE button[edit]

First, you have to make sure you have a process for your Complete button. You could try imitating some of the complete button process like M_Movement_Process. Go to DocAction column of M_Movement table, there you can see the process and how it is built. Just go and zoom and you know what to do ;)

Here's the real deal... You must complete all the needed methods for your M classes. (EX: MMovement) Then implement DocAction (public class MMovement extends X_M_Movement implements DocAction)

The following columns are needed for parent table:

  1. DocStatus
  2. DocAction
  3. DocType (but I you can not put it if you don't need it, just replace the value you have to return for its method)
  4. Description
  5. IsApproved
  6. DocumentNo
  7. Reversal_ID
  8. Processed
  9. Processing
  • At least, these are the fields/columns that I needed to create a simple Complete button with close and complete choices.

The following columns are needed for the child table:

  1. Description
  2. Line
  3. Processed
  4. ReversalLine_ID
  5. Posted

Then, these are the methods you have to have in you M class. (At least, these are the methods I needed to complete the button.) Btw, you can add any code you like, I'm just giving the overview... :)

This is the default flag that was being used, that's why I used this too.

	/**	Just Prepared Flag			*/
	private boolean		m_justPrepared = false;
	/**	Process Message 			*/
	private String		m_processMsg = null;

public MCUSTableA (Properties ctx, int CUS_TableA_ID, String trxName)
		super (ctx, CUS_TableA_ID, trxName);
		if (CUS_TableA_ID == 0)
		//	setC_DocType_ID (0);
			setDocAction (DOCACTION_Complete);	// CO
			setDocStatus (DOCSTATUS_Drafted);	// DR
	}	//	CUS_WorkOrder

public MCUSPallet[] getLines (boolean requery)
		//...your codes...

This process (processIt) is important because it processes DocAction; it is called every time you process a document whether Complete/Prepare/Void/etc... (c/o: Mark Libunao)

public boolean processIt (String processAction)
		m_processMsg = null;
		DocumentEngine engine = new DocumentEngine ((DocAction)this, getDocStatus());
		return engine.processIt (processAction, getDocAction());
	}	//	processIt

public String prepareIt()
		m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_PREPARE);
		if (m_processMsg != null)
			return DocAction.STATUS_Invalid;

		m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_PREPARE);
		if (m_processMsg != null)
			return DocAction.STATUS_Invalid;
		m_justPrepared = true;
		if (!DOCACTION_Complete.equals(getDocAction()))
		return DocAction.STATUS_InProgress;
	}	//	prepareIt

public String completeIt()
		// Re-Check
	    if (!m_justPrepared) {
	        String status = prepareIt();
	        if (!DocAction.STATUS_InProgress.equals(status))
	            return status;

	    m_processMsg = ModelValidationEngine.get().fireDocValidate(this,
	    if (m_processMsg != null)
	        return DocAction.STATUS_Invalid;

	    // simple - we don't check anything here

	    // User Validation
	    String valid = ModelValidationEngine.get().fireDocValidate(this,
	    if (valid != null) {
	        m_processMsg = valid;
	        return DocAction.STATUS_Invalid;

public String getSummary()
		StringBuffer sb = new StringBuffer();
		//	: Total Lines = 123.00 (#1)
		sb.append(": ")
			.append(" (#").append(getLines(false).length).append(")");
		//	 - Description
		if (getDescription() != null && getDescription().length() > 0)
			sb.append(" - ").append(getDescription());
		return sb.toString();
	}	//	getSummary

* 	Reject Approval
* 	@return true if success 
public boolean rejectIt()
		return true;
	}	//	rejectIt

 * 	Reverse Accrual - none
 * 	@return false 
public boolean reverseAccrualIt()
		// Before reverseAccrual
		m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSEACCRUAL);
		if (m_processMsg != null)
			return false;
		// After reverseAccrual
		m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REVERSEACCRUAL);
		if (m_processMsg != null)
			return false;
		return false;
	}	//	reverseAccrualIt

 * 	Close Document.
 * 	@return true if success 
public boolean closeIt()
		// Before Close
		m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_CLOSE);
		if (m_processMsg != null)
			return false;

		// After Close
		m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_CLOSE);
		if (m_processMsg != null)
			return false;

		//	Close Not delivered Qty
		return true;
	}	//	closeIt

 * 	Reverse Correction
 * 	@return false 
public boolean reverseCorrectIt()
	// Before reverseCorrect
	m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSECORRECT);
	if (m_processMsg != null)
		return false;

Ok, it seems like a lot so it's better if you just visit the following function I'll be putting in MMovement class (I used it as my basis).

  • public void addDescription (String description)
  • public void setProcessed (boolean processed)
  • private boolean m_reversal = false; // this is a variable; reversal flag
  • private void setReversal(boolean reversal)
  • private boolean isReversal()
  • public boolean unlockIt()
  • public boolean invalidateIt()
  • public String getDocumentInfo()
  • public int getDoc_User_ID()
  • public boolean approveIt()
  • public int getC_Currency_ID()
  • public boolean reActivateIt()
  • public String getProcessMsg()
  • public boolean voidIt()
  • public File createPDF ()
  • public File createPDF (File file)

After creating all those methods, just do some minor tweaks here and there just to make it work and put all the codes like updates that you want under completeIt() (if there's something you want to be updated after completion), and you're done. :)

Here's the link to the forum where I asked for some help about this button:

Taking a peek in MOrderLine[edit]

Have you ever tried adding a discount field and modifying Purchase Order or Sales Order's Grand Total?

I did that before. Why? I know that ADempiere has its discount schema but the client wants it straight forward discount (number and percentage). They also want an option to compute discount before or after tax. I was successful in creating a callout that will get the total lines, discounts, and taxes. The callout then returned the amount I'm expecting.

During that time, I was so happy about it and has crossed it out in my checklist. So now time for MR, I processed the PO to complete when a problem occurred!

I was so sure my callout updated the Grand Total, but now it's back to its old value!

If you're experiencing the same thing, here's my advice: DO NOT PANIC (^.^)

There's a method in called private boolean updateHeaderTax() that recomputes everything and updates the Grand Total before the process ends.

I don't know if in the newer version this line is updated, but I'm using 354a version when I did this. Here's the sql update statement that triggers the update of Grand Total:

  sql = "UPDATE C_Order i "
   	    + " SET GrandTotal=TotalLines+"
  	    + "(SELECT COALESCE(SUM(TaxAmt),0) FROM C_OrderTax it WHERE i.C_Order_ID=it.C_Order_ID) "
  	    + "WHERE C_Order_ID=" + getC_Order_ID();

Can you see now the problem? If you can't try executing it in your postgres and see what will happen in one PO record or SO record before you process complete button.

If you're using Rules window in System Admin side, I think you have to setup a developer's environment and create a customization.jar to override for this one.

Please follow the ADempiere's best practices if you will be modifying some of the methods:

Other recommended links that might help you:

Create you Dev. Environment:

Create customization.jar:

Hope this helps, like it helped me. :D

Search for forums if you have more questions. Most likely the problem you'll be encountering was asked already ;)

Rule: Script Model Validator[edit]

You want to add a validation without the need to create the customization.jar? Then you can take advantage of the Rule window. Wiki page:

What I'll show here is how to get the value of a custom column and use it for validation...

 if(A_PO. get_ValueAsString("CUS_Classification_ID").equals("1000042") || A_PO. get_ValueAsString("CUS_Classification_ID").equals("1000043"))
     return "OR No. field is mandatory.";
    return "";

By returning empty string, you're saying that there's no error. If you want to return an error, just put something in the string. How to know what methods you can use for the script? Just go to of adempiere. You can search it in google if you don't want to open your IDE anymore.

Other Helpful Links[edit]

Check out some of the documentations: Accounting

Learn how to enable approval: Activate Doc Approval Workflow

Learn how to add user approval: Configure Dynamic Approval

iDempiere Developer Guide for Windows[edit]

iDempiere Business Suite ERP/CRM/SCM done the community way. Focus is on the Community that includes Subject Matter Specialists, Implementors and End-Users.

iDempiere is based on original Compiere/Adempiere plus a new architecture to use state-of-the-art technologies like OSGi, Buckminster, zk6. You can read more here:

For installation guide, you can find it here:

What I'll add in this guide is how I'm able to install it in Windows x64 OS using PostgreSQL database version 9.0.*

Problems encountered and solutions:

  • I had problems downloading the whole code, the "Tip:Zipped Full Repo" really helped. Here's the link:
  • I had problems with my eclipse when I insisted on using Juno instead of Indigo. So make sure you are using the right version of eclipse.
  • Then I encountered problems when I was creating the database. To save some time, I have created the database using pgAdmin based on how the command line was constructed.
  • And I encountered a one big bump... Installing UUID. All instructions are for Linux OS but I am using windows. I was using google to search for some ways to install it, and it is a good thing that someone documented the same problem and posted the solution. You can find the solution here: [9]
  • I run iDempiere in eclipse and encountered an Application error and the answer is posted in this group:!topic/idempiere/KL97e7u0eV8
  • If you run out of memory and encountered a perm gen space error, see this post:!msg/idempiere/BsZb1Dvypi8/nk0U17J1hQgJ
  • I was successful in importing the seed, but when I was browsing the system I realized that it is not enough, so I created a .bat file to manually import all the sql files according to the version. I am currently using 2.0 therefore under idemepiere/migration/ folder I started from i2.0 to i2.0z. I followed the instructions here: and created the .bat for all the *.sql files. I had no time searching for a more efficient way so I went on ahead and created one. I first exported all the files in a text file by typing dir > fileNames.txt, then from the file name I created .bat file.