User:Trifonnt/Howtos Hints

From ADempiere
< User:Trifonnt
Revision as of 06:21, 20 July 2010 by Sarafudheen (Talk) (How to add chache suport for persistent class?)

Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.

Contents

Adempiere Frequently Asked Question

Practical Use of Document Type & Sequence

  • What is its real purpose of Document Sequence

The purpose is to have different sequence (Document No) for each document type. For example: Imagine you want to difference the PO depending on what you buy. You could have the document type "Purchase Order-A" and the "Purchase Order-B".

"Purchase Order-A" --> Sequence (Document no):A00001 "Purchase Order-B" --> Sequence (Document no):B00001

So for the same transaction (Purchase order) you can difference distinct sequence numbers.


How to create new user and restricted access rights?

Define Role for User Create Role:

Click on the General Setup -> Security -> Role
Click on the New button(ex: MyRole).
Select the Organization from drop down.
Enter the Name for role.
Select the User Level from User Level drop down.
Tick on the Manual Checkbox.
Click on the Save button.
Click on the Grant Access button (eg:Access to All for Sale Module).
Select the Module from drop down that you have to include.
Select the Access To from drop down that you have to include.
Click on the OK button.
Click on the Save button.

Click on the Org Access tab.
Select the Organization for newely defined role.
Click on the Save button.

Create User:

Click on the General Setup -> Security -> User.
Select the Organization from organization drop down.
(Note: user organization and define role organization must be same.)
Enter the Name for user(ex: ABC). 
Set the Password for user.
Click on the Save button.

Assign Role to User:

-     Click on the User Roles tab. 
Select Role from drop down(ex:MyRole).
Click on the Save button.

Click on the User tab. Select the Default Role from drop down(ex:MyRole). Select Default Client from drop down. Select the Default Organization from drop down. Click on the Save button. Login as ABC user.


How to modify Invoice(Customer) Posting?

Using Ademepire "GL Distribution"

Default Adempiere Account posting:

----------------------------------------------------------------
Account                     Debit               Credit
----------------------------------------------------------------
Account Receivable         55.000.000         
   A tax                                        5.000.000
   Service Revenue                             50.000.000
----------------------------------------------------------------


Desired Adempiere Account posting:

----------------------------------------------------------------
Account                     Debit               Credit
----------------------------------------------------------------
Account Receivable         52.750.000                           
An Account                  2.250.000 - which is 4.5%x50.000.000
   A tax                                        5.000.000        
   Service Revenue                             50.000.000
----------------------------------------------------------------

Desired functionality can be achieved with Account splitting. Rule must be defined in the system which will split posting into Account Receivable on two account entries.

Show where products are located

Submitted By: Jan Kantert (jab_doa)

Summary: Show where products are located

sf.net tracker: here


Initial Comment: Log in as System Administrator. Select "Application Dictionary" -> "Window, Tab & Field" -> Name = Product

Click on "Tab". Go to the end of the list and add a new row. Zoom in and set Table to "M_Storage_M_Storage", Name to "Located at", Description to "Where are my units located?", Tab Level to "2" and check "read only". Now save and click on "Create Fields". Thats it. Now you can see where your units are when zooming into a product.

Security

Submitted By: Armen (armenrz)

Summary: Security

sf.net tracker: here


1) ADempiere support role based security - Role window. 2) Role Data Access. You can also play around with SQLWhere in Window, Tab & Field.

Example: (instr('@#User_Level@','C')<>0 OR C_BPartner.CreatedBy=@#AD_user_ID@ OR C_BPartner.SalesRep_ID=@#AD_User_ID@)

For pricelist, there's one place you can't hide price from user, which is Product Info. You need to modify some codes to do this. What I did is add IsCanViewPrice option in Role so now each role can be set either they can view price or not.


Bank Transfer tips

Submitted By: usman88

Summary: Bank Transfer tips

sf.net tracker: here

Small tips to handle 1 source Bank to 1 recipient Bank transfer transaction.

1. Create new cash journal.

2. Select appropriate cash book ( in my case i use dummy cash book to serve this transaction)

3. Next go to cash line and create a new line and select cash type as bank transfer.

4. Then select source bank account and type in transfer amount

5. Create 2nd line as in step 3.

6. This time select recipient bank account and type in transfer amount but in negative sign.

7. Back to cash journal tab, complete and post it.

8. To finalize it create, complete and post Bank statement for each bank account.


PS. Above tips can be use for more complex scenario such as transfer from 2 bank to 1 recipient.

Listing a BOM within a BOM

Submitted By: stingreye

Summary: Listing a BOM within a BOM

sf.net tracker: here

Here is the view in Oracle... Because we have so many parts we ended up doing a materialized view that we update with a process in Compiere everytime we

change

a BOM. We did that so we could report off of this view faster.

I am sure someone out here could write a much better query than this but here is what I did. Basically, created a view on top of itself so that the parent records would null. Then used the connect by clause to generate the hierarchy. You will also notice the use of connect_by_root = Top level of bill. Connect_by_ISLEAF = bottom level of bill. Level = what level of the hierarchy.

CREATE OR REPLACE VIEW BOM_INDENTED
(TOPLEVELID, TOPLEVEL, TOPLEVEL_DESC, PRODUCTNUM, PRODDEC, 
 M_PRODUCTBOM_ID, BOM_LEVEL, BOMQTY, DESCRIPTION, AD_CLIENT_ID, 
 AD_ORG_ID, ROWNUMBER, BOM_LEVEL_NUM, BOTTOMCOMPONENT)
AS 

SELECT   CONNECT_BY_ROOT M_ProductBom_ID AS TOPLEVELID,CONNECT_BY_ROOT Value
AS TOPLEVEL,CONNECT_BY_ROOT b.DESCRIPTION AS TOPLEVEL_DESC, Value AS
 ProductNum,
b.DESCRIPTION AS ProdDesc, M_PRODUCTBOM_ID,LPAD(' ',2*(LEVEL-1)) ||
 TO_CHAR(LEVEL)
BOM_Level, BOMQTY, a.DESCRIPTION,a.AD_CLIENT_ID, a.AD_ORG_ID, ROWNUM, LEVEL,
Connect_By_ISLEAF
  FROM 
  
-- part 2 subquerry combining top level and bom to create null to start from
( SELECT
 "M_PRODUCT_BOM_ID","AD_CLIENT_ID","AD_ORG_ID","ISACTIVE",--"CREATED","C
REATEDBY","UPDATED","UPDATEDBY",  (remove becuase unnnecessarry)
"LINE","M_PRODUCT_ID","M_PRODUCTBOM_ID","BOMQTY","DESCRIPTION","BOMTYPE"
FROM M_PRODUCT_BOM

UNION
-- part 1 the tope level querry creating the nulls that is combined in part 2
SELECT M_PRODUCT_BOM_1.M_PRODUCT_BOM_ID, M_PRODUCT_BOM.AD_CLIENT_ID,
M_PRODUCT_BOM.AD_ORG_ID, M_PRODUCT_BOM_1.ISACTIVE,-- M_PRODUCT_BOM_1.CREATED,
M_PRODUCT_BOM_1.CREATEDBY, M_PRODUCT_BOM_1.UPDATED, M_PRODUCT_BOM_1.UPDATEDBY,
(removed unnecessary)
 M_PRODUCT_BOM_1.LINE,M_PRODUCT_BOM_1.M_PRODUCTBOM_ID AS M_PRODUCT_ID,
M_PRODUCT_BOM.M_PRODUCT_ID AS M_PRODUCTBOM_ID,  M_PRODUCT_BOM_1.BOMQTY,
M_PRODUCT_BOM_1.DESCRIPTION, M_PRODUCT_BOM_1.BOMTYPE
FROM M_PRODUCT_BOM LEFT JOIN M_PRODUCT_BOM  M_PRODUCT_BOM_1 ON
M_PRODUCT_BOM.M_PRODUCT_ID = M_PRODUCT_BOM_1.M_PRODUCTBOM_ID
GROUP BY M_PRODUCT_BOM_1.M_PRODUCT_BOM_ID, M_PRODUCT_BOM.AD_CLIENT_ID,
M_PRODUCT_BOM.AD_ORG_ID, M_PRODUCT_BOM_1.ISACTIVE, M_PRODUCT_BOM_1.CREATED,
M_PRODUCT_BOM_1.CREATEDBY, M_PRODUCT_BOM_1.UPDATED, M_PRODUCT_BOM_1.UPDATEDBY,
M_PRODUCT_BOM_1.LINE, M_PRODUCT_BOM.M_PRODUCT_ID,
 M_PRODUCT_BOM_1.M_PRODUCTBOM_ID,
M_PRODUCT_BOM_1.BOMQTY, M_PRODUCT_BOM_1.DESCRIPTION, M_PRODUCT_BOM_1.BOMTYPE
HAVING (((M_PRODUCT_BOM_1.M_PRODUCTBOM_ID) IS NULL))) a
-- part 3 continued
   INNER JOIN M_PRODUCT b
   ON a.M_PRODUCTBOM_ID = b.M_PRODUCT_ID
  START WITH a.M_PRODUCT_ID IS NULL
  CONNECT BY PRIOR  M_PRODUCTBOM_ID = a.M_PRODUCT_ID
  ORDER SIBLINGS BY b.Value
/

If you want to use materialized views... we did it the following way (I really know very little about them, so if someone has a better idea of how to use them for this, it would be appreciated) The materialized view would be

CREATE MATERIALIZED VIEW BOM_INDENTED_MV
(PTX_Path,TOPLEVELID, TOPLEVEL, TOPLEVEL_DESC, PRODUCTNUM, PRODDEC, 
 M_PRODUCTBOM_ID, BOM_LEVEL, BOMQTY, DESCRIPTION, AD_CLIENT_ID, 
 AD_ORG_ID, BOM_LEVEL_NUM, BOTTOMCOMPONENT,TOPLEVEL_ISACTIVE, COMP_ISACTIVE,
TOP_Client)
 
 Build IMMEDIATE
 REFRESH ON DEMAND 
 DISABLE QUERY REWRITE
 
AS 
SELECT Path, TOPLEVELID,TOPLEVEL,TOPLEVEL_DESC, ProductNum, ProdDesc,
M_PRODUCTBOM_ID, BOM_Level, BOMQTY, f1.DESCRIPTION,mp2.AD_CLIENT_ID,
 mp2.AD_ORG_ID,
BomLevelNum, BottomComponent, MP.ISACTIVE AS TOPLEV_ISACTIVE, MP2.ISACTIVE AS
COMP_ISACTIVE, mp.AD_CLIENT_ID AS TOPLEVEL_Client
  FROM (
SELECT DISTINCT SYS_CONNECT_BY_PATH (M_PRODUCTBOM_ID,'/') AS Path,
 CONNECT_BY_ROOT
M_ProductBom_ID AS TOPLEVELID,CONNECT_BY_ROOT Value AS TOPLEVEL,CONNECT_BY_ROOT
b.DESCRIPTION AS TOPLEVEL_DESC, Value AS ProductNum, b.DESCRIPTION AS ProdDesc,
M_PRODUCTBOM_ID,LPAD(' ',2*(LEVEL-1)) || TO_CHAR(LEVEL) BOM_Level, BOMQTY,
a.DESCRIPTION,a.AD_CLIENT_ID, a.AD_ORG_ID, LEVEL AS BomLevelNum,
 Connect_By_ISLEAF
AS BottomComponent
  FROM 
  
-- part 2 subquerry combining top level and bom to create null to start from
( SELECT
 "M_PRODUCT_BOM_ID","AD_CLIENT_ID","AD_ORG_ID","ISACTIVE",--"CREATED","C
REATEDBY","UPDATED","UPDATEDBY",  (remove becuase unnnecessarry)
"LINE","M_PRODUCT_ID","M_PRODUCTBOM_ID","BOMQTY","DESCRIPTION","BOMTYPE"
FROM M_PRODUCT_BOM

UNION
-- part 1 the tope level querry creating the nulls that is combined in part 2
SELECT M_PRODUCT_BOM_1.M_PRODUCT_BOM_ID, M_PRODUCT_BOM.AD_CLIENT_ID,
M_PRODUCT_BOM.AD_ORG_ID, M_PRODUCT_BOM_1.ISACTIVE,-- M_PRODUCT_BOM_1.CREATED,
M_PRODUCT_BOM_1.CREATEDBY, M_PRODUCT_BOM_1.UPDATED, M_PRODUCT_BOM_1.UPDATEDBY,
(removed unnecessary)
 M_PRODUCT_BOM_1.LINE,M_PRODUCT_BOM_1.M_PRODUCTBOM_ID AS M_PRODUCT_ID,
M_PRODUCT_BOM.M_PRODUCT_ID AS M_PRODUCTBOM_ID,  M_PRODUCT_BOM_1.BOMQTY,
M_PRODUCT_BOM_1.DESCRIPTION, M_PRODUCT_BOM_1.BOMTYPE
FROM M_PRODUCT_BOM LEFT JOIN M_PRODUCT_BOM  M_PRODUCT_BOM_1 ON
M_PRODUCT_BOM.M_PRODUCT_ID = M_PRODUCT_BOM_1.M_PRODUCTBOM_ID
GROUP BY M_PRODUCT_BOM_1.M_PRODUCT_BOM_ID, M_PRODUCT_BOM.AD_CLIENT_ID,
M_PRODUCT_BOM.AD_ORG_ID, M_PRODUCT_BOM_1.ISACTIVE, M_PRODUCT_BOM_1.CREATED,
M_PRODUCT_BOM_1.CREATEDBY, M_PRODUCT_BOM_1.UPDATED, M_PRODUCT_BOM_1.UPDATEDBY,
M_PRODUCT_BOM_1.LINE, M_PRODUCT_BOM.M_PRODUCT_ID,
 M_PRODUCT_BOM_1.M_PRODUCTBOM_ID,
M_PRODUCT_BOM_1.BOMQTY, M_PRODUCT_BOM_1.DESCRIPTION, M_PRODUCT_BOM_1.BOMTYPE
HAVING (((M_PRODUCT_BOM_1.M_PRODUCTBOM_ID) IS NULL))) a
-- part 3 continued
   INNER JOIN M_PRODUCT b
   ON a.M_PRODUCTBOM_ID = b.M_PRODUCT_ID
  START WITH a.M_PRODUCT_ID IS NULL
  CONNECT BY PRIOR  M_PRODUCTBOM_ID = a.M_PRODUCT_ID
  ORDER SIBLINGS BY b.Value
) f1
INNER JOIN M_PRODUCT mp ON TOPLEVELID=mp.M_PRODUCT_ID
INNER JOIN M_PRODUCT mp2 ON M_PRODUCTBOM_ID = mp2.M_PRODUCT_ID
ORDER BY Path
/

Then to refresh the materialize view we used the following database procedure where we used a parameter in the "report process" window to identify which MV to refresh:

CREATE OR REPLACE PROCEDURE Indented_Bom_Mv_Refresh
(
	PInstance_ID    		IN NUMBER	
)
AS
--	Logistice
	ResultStr						VARCHAR2(2000);
	Message							VARCHAR2(2000);
	Record_ID						NUMBER;
	--	Parameter
	CURSOR Cur_Parameter (PInstance NUMBER) IS
		SELECT i.Record_ID, p.ParameterName, p.P_String, p.P_Number, p.P_Date
		FROM AD_PINSTANCE i, AD_PINSTANCE_PARA p
		WHERE i.AD_PInstance_ID=PInstance
		AND i.AD_PInstance_ID=p.AD_PInstance_ID(+)
		ORDER BY p.SeqNo;
	--	Parameter Variables
	p_MVIEW						VARCHAR2(2000);


BEGIN
	DBMS_OUTPUT.PUT_LINE('Updating PInstance - Processing ' || PInstance_ID);
    ResultStr := 'PInstanceNotFound';
    UPDATE AD_PINSTANCE
    SET Created = SYSDATE,
        IsProcessing = 'Y'
    WHERE AD_PInstance_ID=PInstance_ID;
    COMMIT;
--	Get Parameters
	ResultStr := 'ReadingParameters';
	FOR p IN Cur_Parameter (PInstance_ID) LOOP
		Record_ID := p.Record_ID;
		IF (p.ParameterName = 'MVIEW') THEN
 			P_PTX_MVIEW := p.P_String;
			DBMS_OUTPUT.PUT_LINE('  MVIEW=' || p_MVIEW);
		ELSE
			DBMS_OUTPUT.PUT_LINE('*** Unknown Parameter=' || p.ParameterName);
	 	END IF;
	END LOOP;	--	Get Parameter
	DBMS_OUTPUT.PUT_LINE('  Record_ID=' || Record_ID);

		
DBMS_MVIEW.REFRESH (p_PTX_MVIEW, 'c');


<<FINISH_PROCESS>>
	--  Update AD_PInstance
	DBMS_OUTPUT.PUT_LINE('Updating PInstance - Finished ' || Message);
    UPDATE  AD_PINSTANCE
    SET Updated = SYSDATE,
        IsProcessing = 'N',
        Result = 1,                 -- success
        ErrorMsg = Message
    WHERE   AD_PInstance_ID=PInstance_ID;
    COMMIT;
    RETURN;

EXCEPTION
    WHEN  OTHERS THEN
		ResultStr := ResultStr || ': ' || SQLERRM || ' - ' || Message;
		DBMS_OUTPUT.PUT_LINE(ResultStr);
        UPDATE  AD_PINSTANCE
        SET Updated = SYSDATE,
            IsProcessing = 'N',
            Result = 0,             -- failure
            ErrorMsg = ResultStr
        WHERE   AD_PInstance_ID=PInstance_ID;
        COMMIT;
        RETURN;
END ;
/

User authentication in Adempiere using LDAP

Submitted By: gemmiti

Summary: User authentication in Adempiere using LDAP

sf.net tracker: here

Enabling LDAP Functionality:

1. Log in as SysAdmin

2. Go to Menu > System Admin > System

3. In Field "Ldap URL" fill in your LDAP URL, i.e. "LDAP://YourLdapServer.com"

4. In field "LDAP Domain" Your Domain, i.e., "YourLdapServer.com"

5. Log Out as SysAdmin

6. Log In as SuperUser/Admin

7. GoTo Menu > General Rules > Security>User

8. In tab "User Contact" and "Internal" you will find a field for LDAP User Name -- Here (for a particular user) fill in the LDAP use name.

Upon logging in -- the user can enter his/her LDAP user name and password and it will associate the correct credentials to the user you have set up in Compiere.

Add OnHandQty In Product window

Submitted By: stingreye

Summary: Add OnHandQty In Product window

sf.net tracker: here

First the SQL 
1)	You want On Hand Qty -  Product can be stored in multiple locators in M_Storage table. I assume you want total of all locators
2)	So SQL = SELECT SUM (QTYONHAND) FROM M_STORAGE s
3)	Now you want to sync with product table,  so  we add WHERE s.M_PRODUCT_ID = M_PRODUCT.M_PRODUCT_ID
4)	So the final SQL we need is (SELECT SUM(QTYONHAND) FROM M_STORAGE s WHERE s.M_PRODUCT_ID = M_PRODUCT.M_PRODUCT_ID)

Now lets add it to compiere:
1)	Log in as SuperUser or System
2)	Role = System Administrator
3)	Open Table window
4)	Select Column tab
5)	Create new record
6)	Insert a  name for DB Column Name 
7)	Choose a system element in this case most likely Qty_OnHand
8)	Reference = String
9)	Length = 20 should be plenty
10)	Click Save
11)	Click synchronize column, cross fingers
12)	Look on bottom left of window for alter table M_PRODUCTADD name of the field you just made

Lets add it to the M_Product window.

1)	Go to Window Tab Field
2)	Choose Product Table
3)	Goto Tab 
4)	Choose the “tab” we want which is Product (it is for the M_PRODUCT table)
5)	Choose Field Tab
6)	New Record
7)	Name your Field (Qty on Hand)
8)	Column – Choose the new column you created.
9)	Save

Now open the Product window and it should work!

How to setup Workflow for PO Approval?


Workflow Simple approval

Workflow Dynamic approval

Changelog functionality

changelog functionality consists of two parts: who & what.

  • Via tables user can identify "what" wish to log.
  • Via roles "who".

By this way is possible to finely control the auditing. This means more tables to be flagged if user wish to log all tables.


Securing JBoss JMX Console and Web Console


SECURE SCENARIO to use Adempiere

Possible SECURE SCENARIO to use Adempiere (applicable Compiere installations also)

CLIENTS

  • 1 - Don't install clients
  • 2 - Install two servers - one for NX (call it NXServer), and the other for oracle and JBoss (call it ADServer)
  • 3 - Allow clients just run through NX connection (you'll have total control of the ADempiere.properties in a linux box)
  • 4 - Configure the ADServer to only accept connections from NXServer
  • 5 - Configure the Oracle in ADServer to only accept connections from the same ADServer and from NXServer

SERVICES ON WEB

  • 1 - Don't expose web services from jboss directly
  • 2 - Install an apache server to expose in a controlled way just the needed services and pages from jboss adempiere


In this scenario NX can be replaced with any terminal server software (Microsoft TS, Citrix, Sun Global Secure Desktop, etc). And you must incur in costs of terminal server software licensing - but security is not free. And you must ensure all those servers - ensure NX, ensure oracle, ensure jboss, ensure apache, ensure linux, etc.

Product Attribute Instance - Color/Size/Serial number


Material transaction between two organizations


Maintain multiple environments(Postgre multiple schema managing)


PostgreSQL Materialized Views

Report sales figures by product

1. Create a Report Line Set called All Products.
2. Create a Report Line under it called Product with a Line Type of Segment Value (leave Posting and Amount Types blank)
3. Create a Report Source under it with Type=Product and leave the Product field blank
4. Create a Report Column Set called Current Period and YTD
5. Create 2 report columns under it, one for Current Period and YTD. The first one should have Posting Type=Actual, Amount Type=Period Balance, Column Type=Relative Period, Relative Period=0. The second one should be the same except Amount Type=Year Balance.
6. Then create a Financial report using these Report Line and Columns Sets. Make sure to check List Sources when you run to see all the products. Warning, If there are more than 1000 products it won't work (we're obviously aware of this limitation and working on removing it).

You can easily produce a similar report for Sales by Business Partner by just substituting Type=Business Parter in Step 3.

How to monitor Oracle sessions?

SELECT SID, SERIAL#, STATUS, SERVER
  FROM V$SESSION
  WHERE USERNAME = 'ADEMPIERE';


How to get Oracle Character set?

 SELECT * FROM nls_database_parameters
 WHERE parameter = any('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET');

How to change document numbers?

Go to Performance

Analysis -> Accounting Rules -> Document Type

Here you can see all existing document types like Purchase Order etc. If the 'Document is Number Controlled' checkbox is checked then you will have a Document Sequence field (a drop down). Right click and zoom to the Document Sequence window

Performance Analysis -> Accounting Rules -> Document Sequence

Here you can change the current next value, the increment and you can add prefix and or suffix for your order/invoice numbers.


Where to place JasperReport files?

JasperReport can be defined as:

http://
https://
attachment:
file:/ 
or a direct file_directory path 

An easier way to deploy jasper reports could be putting the .jasper in the customization.jar

For that we could add a new prefix like:

resource:org/adempiere/jasper/MyReport.jasper


How to handle imprests?


How to process credit card payments from Java GUI?

How a user could create a payment in the client (not the webstore) that will get passed to the payment processor?


Window 'Invoice (Customer)'.

Complete 'Invoice (Customer)'.
Set Payment Type - Press Button which by default has value 'On Credit'.
A new window open and user can change payment Type from 'On Credit' to 'Credit Card'.
Enter Credit Card data.


How to add comment on printed Invoice?

A line without product, charge and qty = 0 will be printed as a comment on invoice.

BOM price explained.

https://sourceforge.net/forum/message.php?msg_id=4740301
BOM Product price is depend of the price of the products which built it. 

For example Product A consist of 1 product B and 1 product C. Product B and C are being priced at USD 5 and USD 6 respectively. The price of Product A would be USD 11. You cannot change it.

Meanwhile we often find a case where the price of Product A IS NOT the price of product B or C combined. It can be higher or lower. I try to add price to product A, which not the combined price but it doesn't work. This is what happen.

I put price of USD 15 for product A. When I created Sales Order, and I chose product A the price was still USD 15. Everything felt right. And then the disaster came. When I click Complete button suddenly the price change to USD 11!


If the BOM is not stocked it is considered a 'basket; and will compute the price based on the component prices.
If you want to assign a price change it to 'stocked'


How to enable Multilingual documents?

Client Window -> Multilingual Documents
Language Window 
Language Maintenance -> Add missing translations


How Cost centers are implemented in Adempiere?

sf.net post Adempiere support cost center using organization setup( have a look at the organization and organization type setup). Also, the transact organization element could be turned on in accounting schema to handle this.


How to setup Extended Document Number Formatting?

Enhance Document No Formatting

@AD_Org_ID<Value>@ is replace by 'HQ' ( AD_Org.Value )

<Value> can be replaced by any other column from AD_Org table.


How to list General Ledger Transactions?


How to setup Manufacturing process

01. Define a Resource as Plant
02. Create your resource Type this will determinate the capacity for your resource
03. Create your resource (Production Line , Work Center , Work Station)
03. Create a Manufacturing workflow (routing) where you define the process to
manufacturing your product, so you need define each step the process and assign
the resource where will execute the work.
04. Create the BOM to manufacturing your product, you need define if will use
BOM or Formulas.
05. Create Manufacturing Document .
06. Use the BOM and workflow to create a new Manufacturing order
07. Change the Document status from draft to in process
08. Use Component check
09. if your component are available then use Print & Release Order
10. use Order Receipt & issue to report the component and finish good.

How to Merge Business Partner entities?

If you merge Business Partners you may need to run: Partner Relations -> Business Partner Rules -> Validate Business Partner

to get the correct open amounts, Life time value, etc...

If you merge Products, first move any inventory out of the product you will be deleting as that is not done in the merge process.

Costing records are not merged!


How to display Key and Name?


How to Delete Client/Tenant

Both scripts are in svn:

http://adempiere.svn.sourceforge.net/viewvc/adempiere/contributions/stuff/

DeleteAdempiereClient.SQL (for oracle)
DeleteAdempiereClient_pg.SQL  (for postgresql)


init.d for centOS


Sql which set QtyInvoice = QtyOrdered for given Organization.

update c_orderline ol set ol.QTYINVOICED=ol.QTYORDERED where C_ORDER_ID in (SELECT C_ORDER_ID FROM C_Order o 
				WHERE DocStatus IN('CO','CL') AND IsSOTrx='Y'
				AND AD_Org_ID=1000001
			 AND EXISTS (SELECT * FROM C_OrderLine ol 
					WHERE o.C_Order_ID=ol.C_Order_ID AND ol.QtyOrdered<>ol.QtyInvoiced) 
				);
        commit;


How to translate root menu in Ademepire Swing GUI?

change class: org.compiere.grid.tree.VTreePanel.java

change method: public boolean initTree (int AD_Tree_ID)

add lines:

m_root = vTree.getRoot();
m_root.setName(Msg.getMsg(Env.getCtx(), vTree.getName() ) ); // translate name of menu.
// m_root.setName(Msg.getMsg(Env.getCtx(), "Menu") ); // @Trifon; this is the hardcoded way.


How to translate root menu in Ademepire Zk Web UI?

change class: webui.desktop.java

change method: doCreatePart()

West w = new West();
layout.appendChild(w);
w.setWidth("300px");
w.setCollapsible(true);
w.setSplittable(true);
w.setTitle("Menu");
w.setTooltiptext("Application Menu");

How to fix issue: Active workflow for this record exists?

Sometimes Adempiere crashes while processing a document. This usually leaves the document jammed in between two states.

If you try to reverse it and rather unhelpful error message says there is an active workflow which must be completed first.

To complete the work flow go to
Workflow -> Workflow process
then in the grid view find the work flow process with who's record_ID is the same as the document in question.

Switch to the tab view and click
Manage Process -> then check 'Abort' -> click okay.

Go back to the document, requery (blue arrows), and you should now be able to reverse the document.


How to setup embedded tab?

Example how to setup UOM Conversion tab as embedded in UOM tab.

Steps:

  • 1

Window: "Unit of Measure"

Tab: "Unit of Measure"

Field: "C_UOM_ID" make it visible.

Field: "Included Tab" set value to: "Conversion"

  • 2

Tab: "Conversion"

Set "Sql Where":

C_UOM_Conversion.C_UOM_ID=@C_UOM_ID@


How to enter Payment from BP to another BP?

easiest - reverse correct the payment and re-enter as if coming from the the
BP with the invoices and enter notes/descriptions to show what happened.
less easy - create an invoice to the BP who paid and put to a Charge pointing
to a suspense account.  enter a credit note to the BP with invoices and put
to same charge then allocate the credit note against the invoices.

Location of Documantation Chapters?

Chapter 3: http://grovers.us/media/Ch_3_BPG_SetUp.pdf Chapter 4A: http://grovers.us/media/Ch4Sales&MarketingPrt1.pdf Chapter 4B: http://grovers.us/media/Ch4SalesOrderPrt2.pdf Chapter 5: http://grovers.us/media/finished.zip Chapter 6: http://grovers.us/media/ch6open_items.pdf

Description of Dunning in ADempiere

ADempiere Dunning

Developer hints

  • class dbPort/org.compiere.process.DocActionTemplate shows a template for DocAction, i think that it must be updated.


Hint-1; UPDATE columns which must have default value or have checked constraint

When developer add new column which is NOT NULL or which has checked constraint, please do not forget to execute proper UPDATE TABLE sql statements.

Example:

ALTER TABLE EXP_FormatLine
ADD IsPartUniqueIndex CHAR(1) CHECK(IsPartUniqueIndex IN ('Y', 'N'));

UPDATE EXP_FormatLine
SET IsPartUniqueIndex = 'N' 
WHERE IsPartUniqueIndex IS NULL;

Hint-2; Be carefull when creating VIEWS

Use LEFT/RIGHT OUTER JOINS, because in many cases fields do not have values and INNER JOIN will not show all records. In this example C_Invoice.C_Order_ID is not manadatory so view will not show records which do not have Order associated with Invoice!

FROM C_Invoice i 
    INNER JOIN C_DocType d ON (i.C_DocType_ID=d.C_DocType_ID) 
    LEFT OUTER JOIN C_Order o ON (i.C_Order_ID=o.C_Order_ID) 

Hint-3; Java on Ubuntu

Ubuntu came with default version of java 1.4.2 and installing 1.5.0 didn't change that. So developer have to change it. Exporting the path to your newer java to the .bashrc config file of your environment is a standadrd decision but is not system wide and in some cases is not enough. The decision is. When you type at the prompt:

  $ sudo update-java-alternatives -l

will see the versions of all javas installed into your system. In order to make the latest one default for the whole system type:

  $ sudo update-java-alternatives -s java-newer-ver 


Hint-4; How to truncate timestamp

Timestamp today = TimeUtil.trunc(new Timestamp (System.currentTimeMillis() ), TimeUtil.TRUNC_DAY);


Hint-5; How to use MTable.get(..) and MTable.getPO(..)

Class: base/src/org/compiere/model/MProduct.java

public MAttributeInstance getAttributeInstance(String name, String trxName) 
{
	MAttributeInstance instance = null;
	
	MTable table = MTable.get(Env.getCtx(), MAttribute.Table_ID);
	MAttribute attribute = (MAttribute)table.getPO("Name = ?", new Object[]{name}, trxName);
	if ( attribute == null ) return null;
	table = MTable.get(Env.getCtx(), MAttributeInstance.Table_ID);
	instance = (MAttributeInstance)table.getPO(
			MAttributeInstance.COLUMNNAME_M_AttributeSetInstance_ID + "=?" 
			+ " and " + MAttributeInstance.COLUMNNAME_M_Attribute_ID + "=?" ,
			new Object[]{getM_AttributeSetInstance_ID(), attribute.getM_Attribute_ID()},
			trxName);
	return instance;
}

Subversion Access

Subversion Plugin for Eclipse Subversive

Adempiere project's SourceForge.net Subversion repository can be checked out through SVN with the following instruction set: svn co https://adempiere.svn.sourceforge.net/svnroot/adempiere adempiere

MFG

sf.net forum post

Important Steps:

1. Apply the following patches: 
 
buildbase.patch.tim 
buildclient.patch.tim 
buildutils_dev.patch.tim 
X_AD_WF_Node.patch 
X_C_DocType.patch 
X_S_Resource.patch 
X_AD_Workflow.patch 
X_M_Cost.patch 
X_M_Transaction.patch 
X_M_Product.patch 
 
2. I copied adempiere/libero/build.xml.tim to adempiere/libero/build.xml 
 
Build your Adempiere and run it.  

3. Create a new client and import accounting etc... 
(If you don't do this you won't see all the menus, you can't just login under GardenAdmin/GardenAdmin that is not enough) 
It looks that 2pack file do not contains proper access records.
Must research!!!

4. Login with your new client Admin login you created in step3 and Libero away!!!


Compiling adempiere + libero from SVN head

Libero Manufacturing functionality is in branch/stable and is not necessary to perform below steps!

1. Check out the head version of both libero and adempiere 
2. Build adempiere 
3. Build libero 
4. Rebuild adempiere (to get the libero.jar into the install) 
5. Set up the database. (Create database, run the pljava deployer, drop the
sqlj schema, import the dump file)
6. Run the 330-trunk database migrations 
7. Copy EE01.zip into the packages directory 
8. Run silent setup and start the app server.
9. Launch the Adempiere client
10. Add the EE01 entity type
11. Pack in the EE01.zip
12. Copy libero.jar into the packages/packages/EE01/lib directory.
13. Re-run silentsetup and start the server.

Template Process and steps to create a process

1)create a java class extends org.compiere.process.SvrProcess

2)Implements two methods void doIt() String prepare()

In Prepare method you can get different parameters ProcessInfoParameter[] para = getParameter(); for (int i = 0; i < para.length; i++) { String name = para[i].getParameterName(); if (para[i].getParameter() == null)

else System.out.println(name); } and in doIt method you try to insert this parameters into your table after you create this class Login with system administrator role

3) 'Report and Process' window define your process with parameter 4) 'Table and Column' window choose your table and create a column with Button Reference

5) Choose your process for this Button

package org.adempiere.process;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;

import org.compiere.model.MClient;
import org.compiere.model.MDocType;
import org.compiere.model.X_C_BPartner_EDI;
import org.compiere.model.X_C_EDIFormat;
import org.compiere.model.X_C_EDIProcessor;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;

/**
 *  @author Trifon Trifonov
 *  @version $Id:$
 */
public class ModelImporter extends SvrProcess
{
	/** Client Parameter			*/
	protected int	p_AD_Client_ID = 0;
	
	/** Business Partner Parameter */
	protected int p_C_BPartner_ID = 0;
	
	/** Document Type Parameter */
	protected int p_C_DocType_ID = 0;
	
	/** Record ID */
	protected int p_Record_ID = 0;
	
	/** Table ID */
	int AD_Table_ID = 0;
	
	/**
	 * 	Get Parameters
	 */
	protected void prepare ()
	{
		
		p_Record_ID = getRecord_ID();
		if (p_AD_Client_ID == 0)
			p_AD_Client_ID = Env.getAD_Client_ID(getCtx());
		AD_Table_ID = getTable_ID();
		
		StringBuffer sb = new StringBuffer ("AD_Table_ID=").append(AD_Table_ID);
		sb.append("; Record_ID=").append(getRecord_ID());
		//	Parameter
		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("C_BPartner_ID"))
				p_C_BPartner_ID = para[i].getParameterAsInt();
			else
				log.log(Level.SEVERE, "Unknown Parameter: " + name);
		}
		
		log.info(sb.toString());
	}

	/**
	 * 	Process 
	 *	@return info
	 */
	protected String doIt () throws Exception
	{
		StringBuffer result = new StringBuffer("");
		
		MClient client = MClient.get (getCtx(), p_AD_Client_ID);
		log.info(client.toString());
		// get proper EDI Format from Document Type
		MDocType docType = MDocType.get(getCtx(), p_C_DocType_ID);
		
		int C_EDIFormat_ID = 0;
		int C_EDIProcessor_ID = 0;
		String sql1 = "SELECT C_EDIFormat_ID, C_EDIProcessor_ID "
			   + "FROM " + X_C_BPartner_EDI.Table_Name + " "
			   + "WHERE AD_Client_ID = ? "
			   + " AND C_BPartner_ID = ? "
			   + " AND C_DocType_ID = ? "
			   + " AND Inbound = 'N' " 
		;
		ResultSet rs1 = null;
		PreparedStatement pstmt1 = null;
		try
		{
			pstmt1 = DB.prepareStatement(sql1, get_TrxName());
			pstmt1.setInt(1, p_AD_Client_ID);
			pstmt1.setInt(2, p_C_BPartner_ID);
			pstmt1.setInt(3, p_C_DocType_ID);
			rs1 = pstmt1.executeQuery();
			if (rs1.next())
			{
				// Found specific C_EDIFormat for given BPartner and C_DocType_ID
				C_EDIFormat_ID = rs1.getInt(X_C_EDIFormat.COLUMNNAME_C_EDIFormat_ID);
				C_EDIProcessor_ID = rs1.getInt(X_C_EDIProcessor.COLUMNNAME_C_EDIProcessor_ID);
			} else {
				// Get C_EDIFormat_ID from Document Type
				C_EDIFormat_ID = docType.getC_EDIFormat_ID();
			}
			
		} finally {
			try {
				if (rs1 != null) rs1.close();
				if (pstmt1 != null) pstmt1.close();
			} catch (SQLException ex) {/*ignored*/}
			rs1 = null;
			pstmt1 = null;
		}
		if (C_EDIFormat_ID == 0) {
			throw new Exception("EDI Format is not set for C_DocType_ID = [" + p_C_DocType_ID + "]");
		}
		if (C_EDIProcessor_ID == 0) {
			throw new Exception("EDI Processor is not set for C_DocType_ID = [" + p_C_DocType_ID + "]");
		}
		
		addLog(0, null, null, Msg.getMsg (getCtx(), "EDISubmitProcessResult") + "\n" + result.toString());
		return result.toString();
	}
}

Migration with SQL statements - Oracle - AD_Element

INSERT INTO AD_Element
(AD_Element_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Cpdated, UpdatedBy,
ColumnName, EntityType, Name,
PrintName
)
VALUES (500XX, 0, 0, 'Y',
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'[ColumnNameHere]', 'D', '[NameHere]',
'[PrintNameHere]'
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Element_ID) + 1
FROM AD_Element
WHERE AD_Element_ID < 1000000)
WHERE NAME = 'AD_Element';


Migration with SQL statements - Oracle - AD_Table

INSERT INTO AD_Table 
(AD_Table_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy, 
Name, Description, Help, TableName, 
IsView, AccessLevel, EntityType, AD_Window_ID, 
AD_Val_Rule_ID, LoadSeq, IsSecurityEnabled, IsDeleteable, 
IsHighVolume, ImportTable, IsChangelog, ReplicationType, 
PO_Window_ID, CopyColumnsFromTable
) 
VALUES 
(53014, 0, 0, 'Y', 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'[NameHere]', '[DescriptionHere]', '[HelpHere]', '[TableNameHere]', 
'N', '4', 'D', null, 
null, 0, 'N', 'Y', 
'N', 'N', 'N', 'L', 
null, 'N'
);

COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Table_ID) + 1
FROM AD_Table
WHERE AD_Table_ID < 1000000)
WHERE NAME = 'AD_Table';

Migration with SQL statements - Oracle - AD_Column

INSERT INTO AD_Column
(AD_Column_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created,
Updated, CreatedBy,
UpdatedBy, Name, Description, Version,
EntityType, ColumnName, AD_Table_ID, AD_Reference_ID, 
FieldLength, IsKey, IsParent, IsMandatory, IsUpdateable,
IsIdentifier, SeqNo, IsTranslated, IsEncrypted,
IsSelectionColumn, AD_Element_ID, Callout, IsSyncDatabase,
IsAlwaysUpdateable
)
VALUES (502XX, 0, 0, 'Y',
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'),
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
100, '[NameHere]', '[DescriptionHere]', 1,
'D', '[ColumnNameHere]', AD_Table_IDHERE, AD_Reference_IDHere,
1, 'N', 'N', 'Y', 'Y',
'N', 0, 'N', 'N',
'N', 50071, '[CalloutHere]', 'N',
'N'
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Column_ID) + 1
FROM AD_Column
WHERE AD_Column_ID < 1000000)
WHERE NAME = 'AD_Column';

Migration with SQL statements - Oracle - AD_Window

INSERT INTO AD_Window
(AD_Window_ID,AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy, 
Name, Description, Help, WindowType, 
IsSoTrx, EntityType, Processing, AD_Image_ID, 
AD_Color_ID, IsDefault, WinHeight, WinWidth, 
IsBetaFunctionality
) 
VALUES (53003, 0, 0, 'Y', 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
'[NameHere]', null, null, 'M', 
'N', 'D', 'N', null, 
null, 'N', 0, 0, 
'N'
);

COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Window_ID) + 1
  FROM AD_Window
  WHERE AD_Window_ID < 1000000)
WHERE Name = 'AD_Window';


Migration with SQL statements - Oracle - AD_Tab

INSERT INTO AD_Tab 
(AD_Tab_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy,
Name, Description, Help, AD_Table_ID, 
AD_Window_ID, SeqNo, TabLevel, IsSingleRow, 
IsInfoTab, IsTranslationTab, IsReadOnly, AD_Column_ID, 
HasTree, WhereClause, OrderByClause, CommitWarning, 
AD_Process_ID, Processing, AD_Image_ID, ImportFields, 
AD_ColumnSortOrder_ID, AD_ColumnSortYesNo_ID, IsSortTab, EntityType, 
Included_Tab_ID, ReadOnlyLogic, DisplayLogic, IsInsertRecord, 
IsAdvancedTab
) 
VALUES (53014, 0, 0, 'Y', 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
'[NameHere]', null, null, [AD_TableIDHERE], 
[AD_Window_IDHERE], [SeqNoHERE], 0, 'N', 
'N', 'N', 'N', null, 
'N', null, null, null, 
null, 'N', null, 'N', 
null, null, 'N', 'D', 
null, null, null, 'Y', 
'N'
);

COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Tab_ID) + 1
  FROM AD_Tab
  WHERE AD_Tab_ID < 1000000)
WHERE Name = 'AD_Tab';


Migration with SQL statements - Oracle - AD_Field

INSERT INTO AD_Field
(AD_Field_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy,
Name, Description, IsCentrallyMaintained, SeqNo, AD_Tab_ID,
AD_Column_ID, IsDisplayed, DisplayLength, IsReadonly,
IsSameLine, IsHeading, IsFieldOnly, IsEncrypted, EntityType
)
VALUES (50184, 0, 0, 'Y',
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'Store Archive On File System', 'Store Archive On File System', 'Y', 250, 145,
50214, 'Y', 1, 'N',
'N', 'N', 'N', 'N', 'D'
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Field_ID) + 1
  FROM AD_Field
  WHERE AD_Field_ID < 1000000)
WHERE Name = 'AD_Field';


Migration with SQL statements - Oracle - AD_Menu

INSERT INTO AD_Menu
(AD_Menu_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy,
Name, Description, IsSummary, IsSoTrx, 
IsReadOnly, Action, AD_Window_ID, AD_Workflow_ID, 
AD_Task_ID, AD_Process_ID, AD_Form_ID, AD_Workbench_ID, 
EntityType
)
 VALUES( 53012, 0, 0, 'Y',
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100, 
'[NameHERE]', NULL, 'N', 'N', 
'N', 'W', [AD_Window_IDHERE], NULL, 
NULL, NULL, NULL, NULL, 
'D'
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Menu_ID) + 1
  FROM AD_Menu
  WHERE AD_Menu_ID < 1000000)
WHERE Name = 'AD_Menu';


Migration with SQL statements - Oracle - AD_TreeNodeMM

INSERT INTO AD_TreeNodeMM 
(AD_Tree_ID, Node_ID, AD_Client_ID, AD_Org_ID, IsActive, 
Created, Createdby, 
Updated, UpdatedBy, Parent_ID, SeqNo
)
VALUES (10, [Node_IDHERE], 0, 0, 'Y', 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 0, 
TO_DATE ('10/22/2007 09:30:00', 'MM/DD/YYYY HH24:MI:SS'), 0, [Parent_IDHERE], [SeqNoHERE]
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Menu_ID) + 1
  FROM AD_Menu
  WHERE AD_Menu_ID < 1000000)
WHERE Name = 'AD_Menu';

Migration with SQL statements - AD_Message

INSERT INTO AD_Message
(AD_Message_ID, AD_Client_ID, AD_Org_ID, IsActive,
Created, CreatedBy,
Updated, UpdatedBy,
Value, MsgText, MsgType
)
VALUES (500XX, 0, 0, 'Y',
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('02/26/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'[VaueHere]','[MSG Text Here]','I'
);
COMMIT;
UPDATE AD_Sequence
SET CurrentNextSys = (SELECT MAX (AD_Message_ID) + 1
FROM AD_Message
WHERE AD_message_ID < 1000000)
WHERE NAME = 'AD_Message';

Migration with SQL statements - AD_Process_Para

INSERT INTO AD_Process_Para
(AD_Process_Para_ID, AD_Client_ID, AD_Org_ID, IsActive, Created,
CreatedBy, Updated, UpdatedBy, Name,
Description,
Help,
AD_Process_ID, SeqNo, AD_Reference_ID, AD_Reference_Value_ID,
AD_Val_Rule_ID, ColumnName, IsCentrallyMaintained, FieldLength,
IsMandatory, IsRange, AD_Element_ID, EntityType
)
VALUES (50019, 0, 0, 'Y', TO_DATE ('2007-03-03', 'YYYY-MM-DD'),
100, TO_DATE ('2007-03-03', 'YYYY-MM-DD'), 100, 'Shipment Date',
'Date printed on shipment',
'The Shipment Date indicates the date printed on the shipment.',
118, 15, 15, NULL,
NULL, 'MovementDate', 'N', 0,
'Y', 'N', 1037, 'D'
);
COMMIT;
UPDATE AD_SEQUENCE
SET currentnextsys = (SELECT MAX (ad_process_para_id) + 1
FROM AD_Process_Para
WHERE AD_Process_Para_ID < 1000000)
WHERE NAME = 'AD_Process_Para';


Migration with SQL statements - AD_Ref_List - Postgre

INSERT INTO AD_Ref_List (
Created, CreatedBy, Updated, UpdatedBy, 
AD_Org_ID, AD_Ref_List_ID, AD_Reference_ID, 
Value, Name, IsActive, AD_Client_ID, EntityType
) VALUES (
TO_TIMESTAMP('2008-01-19 22:45:00','YYYY-MM-DD HH24:MI:SS'), 0, TO_TIMESTAMP('2008-01-19 22:45:00','YYYY-MM-DD HH24:MI:SS'), 0, 
0, 53287, 329, 
'DOO', 'Distribution Order', 'Y',0,'EE01'
);

Session_IsChangeLog, AD_Table.IsChangeLog, AD_Column.IsChangeLog combination

1. Session_IsChangeLog=no,  AD_Table.IsChangeLog=no,  AD_Column.IsChangeLog=no
     =>is column logged: no
2. Session_IsChangeLog=no,  AD_Table.IsChangeLog=no,  AD_Column.IsChangeLog=yes
     =>is column logged: no
3. Session_IsChangeLog=no,  AD_Table.IsChangeLog=yes, AD_Column.IsChangeLog=no
     =>is column logged: no
4. Session_IsChangeLog=no,  AD_Table.IsChangeLog=yes, AD_Column.IsChangeLog=yes
     =>is column logged: yes
5. Session_IsChangeLog=yes, AD_Table.IsChangeLog=no,  AD_Column.IsChangeLog=no
     =>is column logged: no
6. Session_IsChangeLog=yes, AD_Table.IsChangeLog=no,  AD_Column.IsChangeLog=yes
     =>is column logged: yes
7. Session_IsChangeLog=yes, AD_Table.IsChangeLog=yes, AD_Column.IsChangeLog=no
     =>is column logged: no
8. Session_IsChangeLog=yes, AD_Table.IsChangeLog=yes, AD_Column.IsChangeLog=yes
     =>is column logged: yes

Note: Session_IsChangeLog means: AD_Role.IsChangeLog or is a webstore session.

Observation1: as you see, AD_Column.IsChangeLog=Y means inherit policy from
parent (AD_Table) and AD_Column.IsChangeLog=N means never log this record.

PS: in future, i think we can move from IsChangeLog flag to some
ChangeLogPolicy=(Always, Never, Inherit from parent).


How to use Transactions and Save points in Adempiere

String trxName = Trx.createTrxName("test");
Trx trx = Trx.get(trxName, true);
Savepoint save1 = trx.setSavepoint("save1");

location = new MLocation(m_Ctx, 0, trxName);

... 

trx.rollback(save1);

trx.commit();
trx.close();


How to build Adempiere customization.jar

Customization.jar is in the lib directory of trunk. (ie /trunk/lib ) Go to that directory and execute following commands:

jar uvf customization.jar -C ../libero/build/ ./org/compiere/model/ jar uvf customization.jar -C ../libero/build/ ./org/compiere/acct/ jar uvf customization.jar -C ../libero/build/ ./org/compiere/process/

Then goto utils_dev directory and ./RUN_build.sh

After the build has completed the new customized.jar will be in $ADEMPIERE_HOME/lib Adempiere should now use the customized classes.


How to Open Window from source code?

MQuery aQuery = new  MQuery ("C_Invoice");
aQuery.setRecordCount(1);
aQuery.addRestriction("C_Invoice_ID", "=", C_Invoice_ID );

AWindow frame = new AWindow();
frame.initWindow(183, aQuery); // 183=window "Invoice (Vendor)"

frame.validate();
AEnv.showCenterScreen(frame);


How to keep parameter window open after view report?


How to create confirmation for inventory move?


How to make Adempiere LDAP username registration compatible with OpenLDAP

- StringBuffer principal = new StringBuffer (userName).append("@").append(domain);
+ // For OpenLDAP uncomment the next line  
+ // StringBuffer principal = new StringBuffer("uid=").append(userName).append(",").append(domain);
+ StringBuffer principal = new StringBuffer(userName).append("@").append(domain);
  env.put(Context.SECURITY_PRINCIPAL, principal.toString());
  env.put(Context.SECURITY_CREDENTIALS, password);


How to use JSR 223: Scripting callouts in Adempiere?

Integer order_id = (Integer) mTab.getValue("C_ORDER_ID");
MOrder obj = new MOrder(Env.getCtx(),order_id.intValue(),null);
if (obj.getC_DocType_ID()==1000025 )
{
  your code here...
}

How to get Adempiere/Compiere Home from java class?

org.compiere.util.Ini.findAdempiereHome()


How to set/update column ModelValidationClasses

Nice code from Carlos! Have to check if it works for Oracle and Postgre.

UPDATE AD_Client
SET ModelValidationClasses =
CASE
WHEN ModelValidationClasses LIKE '%org.adempiere.model.LCO_Validator%'
  THEN ModelValidationClasses
WHEN LENGTH (TRIM (ModelValidationClasses)) = 0 OR ModelValidationClasses IS NULL
  THEN TO_NCHAR ('org.adempiere.model.LCO_Validator')
ELSE 
   ModelValidationClasses || ';org.adempiere.model.LCO_Validator'
END
WHERE AD_Client_ID != 0


How to use Adempiere sequence from java code?

	MSequence sequence = new MSequence(getCtx(), sequenceId, get_TrxName());
	int nextId = sequence.getNextID();
	sequence.save();

How to setup Adempiere ZK Development Environment


How to add new tab to Info Window


How to copy print format and how to invoke print preview from java?

Language language = Language.getLoginLanguage(); // Base Language
		MPrintFormat pf = null;
		int pfid = 0;
		
		// get print format for client, else copy system to client  
		RowSet pfrs = MPrintFormat.getAccessiblePrintFormats(X_RV_PP_Product_BOMLine.Table_ID, -1, null);
		pfrs.next();
		pfid = pfrs.getInt("AD_PrintFormat_ID");
		
		if(pfrs.getInt("AD_Client_ID") != 0) pf = MPrintFormat.get(getCtx(), pfid, false);
		else pf = MPrintFormat.copyToClient(getCtx(), pfid, getAD_Client_ID());
		pfrs.close();		

		if (pf == null) raiseError("Error: ","No Print Format");

		pf.setLanguage(language);
		pf.setTranslationLanguage(language);
		// query
		MQuery query = MQuery.get(getCtx(), AD_PInstance_ID, X_RV_PP_Product_BOMLine.Table_Name);
		query.addRestriction("AD_PInstance_ID", MQuery.EQUAL, AD_PInstance_ID);

		PrintInfo info = new PrintInfo(X_RV_PP_Product_BOMLine.Table_Name, 
				X_RV_PP_Product_BOMLine.Table_ID, getRecord_ID());
		ReportEngine re = new ReportEngine(getCtx(), pf, query, info);

		ReportCtl.preview(re);
		// wait for report window to be closed as t_bomline   
		// records are deleted when process ends 
		while (re.getView().isDisplayable()) 
		{
			Env.sleep(1);
		}


Some views

CREATE VIEW RV_OpenItemAcct AS
SELECT 
  item.AD_Client_ID
, item.AD_Org_ID
, item.DocumentNo
, item.C_Invoice_ID
, item.C_Order_ID
, item.C_BPartner_ID
, item.IsSOTrx
, item.DateInvoiced
, item.DateAcct
, item.NetDays
, item.DueDate
, item.DaysDue
, item.DiscountDate
, item.DiscountAmt
, item.GrandTotal
, item.PaidAmt
, item.OpenAmt
, item.C_Currency_ID
, item.C_ConversionType_ID
, item.C_PaymentTerm_ID
, item.IsPayScheduleValid
, item.C_InvoicePaySchedule_ID
, item.InvoiceCollectionType
, item.C_Campaign_ID
, item.C_Project_ID
, item.C_Activity_ID
, combCust.Combination AS custCombComination
, combCust.Description AS custCombDescription
, combVend.Combination AS custVendComination
, combVend.Description AS custVendDescription
FROM RV_OpenItem item
LEFT OUTER JOIN C_BP_Customer_Acct custAcct ON (item.IsSoTrx= 'Y' AND item.C_BPartner_ID = custAcct.C_BPartner_ID AND custAcct.C_AcctSchema_ID=101)
LEFT OUTER JOIN C_ValidCombination combCust ON (custAcct.C_Receivable_Acct = combCust.C_ValidCombination_ID)
LEFT OUTER JOIN C_BP_Vendor_Acct vendAcct ON (item.IsSoTrx= 'N' AND item.C_BPartner_ID = vendAcct.C_BPartner_ID AND vendAcct.C_AcctSchema_ID=101)
LEFT OUTER JOIN C_ValidCombination combVend ON (vendAcct.V_Liability_Acct = combVend.C_ValidCombination_ID)


How to call stored procedure from java?

CallableStatement cs = null;
  cs = DB.prepareCall("{call AD_Sequence_Next(?,?,?)}");
  cs.setString(1, "A_Asset_Change");
  cs.setInt(2,imp.getAD_Client_ID());
  cs.registerOutParameter(3, java.sql.Types.INTEGER);
  cs.execute();
  
  int value = cs.getInt(3);


How to add Value column to Existing Adempiere table?

  • Parameters:
    • TableName = 'M_Attribute'
    • AD_Client_ID = 11
  • Add Value column
ALTER TABLE M_Attribute
ADD Value NVARCHAR2(40)
  • Make values unique
    • Make values unique - version 1
UPDATE M_Attribute
 Set Value = nextidfunc( (SELECT AD_Sequence_ID FROM AD_Sequence WHERE AD_Client_ID = 0 AND Name = 'M_Attribute')::integer, 'N'::varchar)
WHERE M_Attribute.Value IS NULL
 AND M_Attribute.AD_Client_ID = 11
    • Make values unique - version 2
UPDATE M_Attribute
 Set Value = nextidfunc(691, 'N'::varchar)
WHERE M_Attribute.Value IS NULL
 AND M_Attribute.AD_Client_ID = 11
  • Add Unique contraint
    • Add Unique contraint - version 1
ALTER TABLE M_Attribute
 ADD Constraint M_Attribute_Value UNIQUE(AD_Client_ID, Value)
    • Add Unique contraint - version 2
CREATE UNIQUE INDEX M_Attribute_Value ON M_Attribute(AD_Client_ID, Value)

How to speedup Record Change Log?

create index ad_changelog_speed on ad_changelog (ad_table_id, record_id);
    • Postgre
create index ad_changelog_speed on ad_changelog (ad_table_id, record_id);


Log management in Adempiere?

Adempiere uses Java logging capabilities. Responsible classes are:

base/src/org.compiere.util.CLogMgt

Configuration files:

base/src/org/compiere/util/logClient.properties
base/src/org/compiere/util/logServer.properties


Adempiere initiative: Remove SQL code and Replace with Query class!

  • Description to use in svn commits:
One class per day initiative.
FR: [ 2214883 ] Remove SQL code and Replace for Query
https://sourceforge.net/tracker/?func=detail&atid=879335&aid=2214883&group_id=176962

How to use Adempiere Query class?

How to return only ONE object?
StringBuffer whereClause = null;
whereClause = new StringBuffer("AD_Client_ID=?"			// #1
							 + " AND AD_Org_ID=?"		// #2
							 + " AND C_AcctSchema_ID=?"	// #3
							 + " AND Account_ID=?");	// #4

ArrayList<Object> params = new ArrayList<Object>();
params.add(AD_Client_ID);
params.add(AD_Org_ID);
params.add(C_AcctSchema_ID);
params.add(Account_ID);

Account existingAccount = new Query(ctx, MAccount.Table_Name, whereClause.toString(), null)
		.setParameters( params )
		.first();


How to return ARRAY of objects?
public static MAchievement[] getOfMeasure (Properties ctx, int PA_Measure_ID)
{
	String whereClause = "PA_Measure_ID=? AND IsAchieved='Y'"; 
	List<MAchievement> list = new Query(ctx, MAchievement.Table_Name, whereClause, null)
		.setParameters(new Object[]{PA_Measure_ID})
		.setOrderBy("SeqNo, DateDoc")
		.list()
	;
			
	MAchievement[] retValue = new MAchievement[ list.size() ];
	retValue = list.toArray (retValue);
	return retValue;
}
How to return ARRAY of objects and process elements?
public static MAchievement[] getOfMeasure (Properties ctx, int PA_Measure_ID)
{
	String whereClause = "PA_Measure_ID=? AND IsAchieved='Y'"; 
	List<MAchievement> list = new Query(ctx, MAchievement.Table_Name, whereClause, null)
		.setParameters(new Object[]{PA_Measure_ID})
		.setOrderBy("SeqNo, DateDoc")
		.list()
	;
	for(MAchievement achievement : list)
	{
	  s_log.fine(" - " + achievement);
	}

	MAchievement[] retValue = new MAchievement[ list.size() ];
	retValue = list.toArray (retValue);
	return retValue;
}
How to pass Timestamp parameter?
Timestamp dateAcct = ...;
String trxName = ...;

String whereClause = "c.C_CashBook_ID=?"		//	#1
		+ " AND TRUNC(c.StatementDate)=?"	//	#2
		+ " AND c.Processed='N'";
		
MCash retValue = new Query(ctx, MCash.Table_Name, whereClause, trxName)
	.setParameters(new Object[]{C_CashBook_ID, TimeUtil.getDay(dateAcct)})
	.first()
;


How to use Query class with complex where clause: EXISTS?
String whereClause = "C_Cash.AD_Org_ID=?"		//	#1
+ " AND TRUNC(C_Cash.StatementDate)=?"			//	#2
+ " AND C_Cash.Processed='N'"
+ " AND EXISTS (SELECT * FROM C_CashBook cb "
	+ "WHERE C_Cash.C_CashBook_ID=cb.C_CashBook_ID AND cb.AD_Org_ID=C_Cash.AD_Org_ID"
	+ " AND cb.C_Currency_ID=?)"			//	#3
;
MCash retValue = new Query(ctx, MCash.Table_Name, whereClause, trxName)
	.setParameters(new Object[]{AD_Org_ID,TimeUtil.getDay(dateAcct),C_Currency_ID})
	.first()
;

How to use Adempiere MQuery class?

  • example 1
MQuery aQuery = new MQuery(X_C_Invoice.Table_Name);
aQuery.setRecordCount( 1 );
aQuery.addRestriction("C_Invoice_ID", "=", C_Invoice_ID );
  • Example 2
MQuery query = MQuery.get(getCtx(), AD_PInstance_ID, X_RV_PP_Product_BOMLine.Table_Name);
query.addRestriction("AD_PInstance_ID", MQuery.EQUAL, AD_PInstance_ID);


Adempiere utility classes?

  • base/org.compiere.util.TimeUtil

Base Language functionality

Revision: 6839
         http://adempiere.svn.sourceforge.net/adempiere/?rev=6839&view=rev

Revision: 6810
         http://adempiere.svn.sourceforge.net/adempiere/?rev=6810&view=rev
   contributions/Localizations/Germany/src/migration/postgresql/baselanguage/baselanguage.sql
   contributions/Localizations/Germany/src/migration/postgresql/baselanguage/baselanguage_views.sql
Revision: 6809
         http://adempiere.svn.sourceforge.net/adempiere/?rev=6809&view=rev
   contributions/Localizations/Germany/src/java/org/compiere/apps/ALogin.java
   contributions/Localizations/Germany/src/java/org/compiere/cm/utils/CMEnv.java
   contributions/Localizations/Germany/src/java/org/compiere/util/Language.java
   contributions/Localizations/Germany/src/java/org/compiere/wstore/PriceListTag.java
   contributions/Localizations/Germany/src/java/org/posterita/core/businesslogic/ElementManager.java

Differences in source code between Adempiere 3.5.0 and Compiere 3.0.0

Context
  • Adempiere
Env.setContext(Env.getCtx(), "#AD_User_ID", CreatedBy_ID);
  • Compiere
Env.getCtx().setContext("#AD_User_ID", CreatedBy_ID);
Build, Imports
  • Compiere
import org.compiere.util.Ctx;
import org.compiere.framework.PO;

add projects 'ad' and 'common' to build path.


Keeping sources compatible with Adempiere 3.5.0 and Compiere 3.0.0


Context
/* $if isAdempiere $
Env.setContext(Env.getCtx(), "#AD_Org_ID", AD_Org_ID);  // Adempiere
$else$ */
Env.getCtx().setContext("#AD_Org_ID", AD_Org_ID);       // Compiere
/* $endif$ */

Example
			// =================
			/* $if isAdempiere $
			Env.setContext(Env.getCtx(), "#AD_User_ID", CreatedBy_ID);  // Adempiere
			$else$ */
			Env.getCtx().setContext("#AD_User_ID", CreatedBy_ID);       // Compiere
			/* $endif$ */
		}
		// =================
		/* $if isAdempiere $
		Env.setContext(Env.getCtx(), "#AD_Client_ID", AD_Client_ID);  // Adempiere
		$else$ */
		Env.getCtx().setContext("#AD_Client_ID", AD_Client_ID);       // Compiere
		/* $endif$ */
	    // =================
	    /* $if isAdempiere $
		Env.setContext(Env.getCtx(), "#AD_Org_ID", AD_Org_ID);  // Adempiere
		$else$ */
		Env.getCtx().setContext("#AD_Org_ID", AD_Org_ID);       // Compiere
		/* $endif$ */


How to Validate conditional mandatory field?

Timestamp start = getTimeSlotStart();
if (start == null)
	throw new FillMandatoryException(COLUMNNAME_TimeSlotStart);


How to enable Query TimeOut in Adempiere?

Specify JVM parameter

-Dorg.adempiere.po.useTimeoutForUpdate=true


How to decrease number of log files created by Java WebStart?

Problem is missing ADEMPIERE_HOME on the machine.
Webstart works fine without environment variable set, only resulting in many many logfiles.
After setting the variable to c:\adempiere it created the log folder and used just one file per session as expected.


How to post accounting transactions without Application Server?

Add new jvm system property:

org.adempiere.server.embedded

License to use for new classes

/**********************************************************************
* This file is part of ADempiere Business Suite                       *
* http://www.adempiere.org                                            *
*                                                                     *
* Copyright (C) Trifon Trifonov.                                      *
* Copyright (C) Contributors                                          *
*                                                                     *
* This program is free software; you can redistribute it and/or       *
* modify it under the terms of the GNU General Public License         *
* as published by the Free Software Foundation; either version 2      *
* of the License, or (at your option) any later version.              *
*                                                                     *
* This program is distributed in the hope that it will be useful,     *
* but WITHOUT ANY WARRANTY; without even the implied warranty of      *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the        *
* GNU General Public License for more details.                        *
*                                                                     *
* You should have received a copy of the GNU General Public License   *
* along with this program; if not, write to the Free Software         *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
* MA 02110-1301, USA.                                                 *
*                                                                     *
* Contributors:                                                       *
* - Trifon Trifonov (trifonnt@users.sourceforge.net)                  *
*                                                                     *
* Sponsors:                                                           *
* - Company (http://www.site.com)                                     *
***********************************************************************/
...
...
/**
 *	@author Trifon Trifonov
 *	@version $Id$
 */

How to add new Actions to Document Engine

Edit class

base/src/org/compiere/process/DocumentEngine.java

Add new if statements:

+               /********************
+                *  Manufacturing Order
+                */
+               else if (AD_Table_ID == MPPOrder.Table_ID)
+               {
+                       if (docStatus.equals(DocumentEngine.STATUS_Drafted)
+                                       || docStatus.equals(DocumentEngine.STATUS_InProgress)
+                                       || docStatus.equals(DocumentEngine.STATUS_Invalid))
+                               {
+                                       options[index++] = DocumentEngine.ACTION_Prepare;
+                                       options[index++] = DocumentEngine.ACTION_Close;
+                               }
+                               //      Complete ... CO
+                               else if (docStatus.equals(DocumentEngine.STATUS_Completed))
+                               {
+                                       options[index++] = DocumentEngine.ACTION_Void;
+                                       options[index++] = DocumentEngine.ACTION_ReActivate;
+                               }
+               }

Adempiere Export/Import Tools

  • ADCK can be used in two modes.
    • As Development tool. To model DB design and create/modify tables.

As Development tool ADCK uses Druid which allows developer to generate CREATE TABLE and ALTER table SQL statement for various DBs: Oracle, Postgres, MySQL, ... As Development tool ADCK can generate xml files for various tools and frameworks like: hibernate, torque.

    • As transfer tool for only migrating AD changes from Adempiere instance to instance or from version to version.

As transfer tool ADCK require initial just setting of proper settings in properties files.

2Pack vs. ADCK comparison table.

This table is made with the sole purpose to help me to organize information regarding import/export tools in Adempiere. I do not have any idea to push any of the tools mentiod here. It is user's choice to use or not to use any of them and also users choice to have headache after thier usage.

WARNING: !!!MAKE A BACKUP BEFORE ANY USAGE OF ANY OF THE TOOLS!!!

2PackADCK
menumenu
windowwindow
tabtab
fieldfield
processprocess
processparaprocesspara
tabletable
columncolumn
impformatAD_ImpFormat
impformatrowAD_ImpFormat_Row
printformatAD_PrintFormat
printformatitemAD_PrintFormatItem
referenceADCK
referencelistADCK
referencetableADCK
reportviewADCK
reportviewcolADCK
taskADCK
formADCK
workbenchADCK
preferenceADCK
roleAD_Role
???AD_User
userroleAD_User_Roles
orgroleAD_Role_OrgAccess
windowaccessAD_Window_Access
processaccessAD_Process_Access
formaccessAD_Form_Access
workflowaccessAD_Workflow_Access
taskaccessADCK
???AD_Column_Access
???AD_Language
???AD_Message
???AD_PrintTableFormat
???AD_Scheduler
???C_BP_Group
???M_Product_Category
???PA_ReportColumnSet
???PA_ReportColumn
???PA_Report
???PA_ReportLineSet
???PA_ReportLine
???PA_ReportSource
???AD_Client
2Pack Any TableADCK Any Table
???TEMPLATE

Weak sides of 2pack according me

  • Require integration at source code level with Adempiere, which means that if i need to customize any import functionality i must rebuild whole application or patch it.
    • Carlos pointed that 2Pack can import records into any table, even customized.
    • My comment is regarding main xml elements like: menu, window, tab, field, process, ... First time when i understood that i need to modify main xml elements was for JasperReports integration when i had to add new column into AD_Process table.
    • I think that GenericPO which is responsible to import records into Any Table had one issue (I have to remember the exact reason. It was regarding Sequences or something related. ).
    • Format of general imports is complicated.

  • Main class(org.adempiere.pipo.PackInHandler) which handle import is very big(3800 lines) and complex, which require lot of time for developer to study it and makes modifications hard task.
    • Since version 3.3.0 Low Heng Sin refactored org.adempiere.pipo.PackInHandler and now it is split into multiple classes.
  • 2Pack uses SAX Parser which makes developer job harder as developer can't look forward or backward in XML tree.

Weak sides of ADCK according me

  • It is external program, so user do not have such detailed control over security.
  • It is harder for developer to work with it as do not have automatic changes extraction like 2Pack.

Ideas for extensions

SOUNDX support


Computerized Maintenance Management System

sf.net post CMMS is Computerized Maintenance Management System that designed specially for preventive maintenance for assets such as light vehicle, heavy equipment etc. so that company can create budgeting for repair and maintenance for their assets. Technicaly the module give alert to user when an asset have to get action such as replace fuel filter, oil filter or other item spareparts. Therefore these module related with inventory (internal use). Inventory will decrease its stock when user CMMS request for repair and maintenance their an asset.


Additional fields in I_Xxx tables

sf.net forum post

By: Carlos (globalqss)

Hi, every time I customize tables for a customer (almost all projects) I need to change the importers and/or make some tricks with not used fields on import. [Struggling today with this case]

Idea!

Not tested, but I think I could make a ModelValidator on udpate of I_ table (i.e. I_BPartner) and process the corresponding fields.

The other thing we could do is to add some free fields to every I_ table as wildcards to save customized or non considered fields and process them properly with the ModelValidator. Maybe just adding i.e. 10 fields FreeField01 through FreeField10 on the tables can make the trick.

Recent Adempiere changes

Avoid usage of clearing accounts

Tracker also contains a .doc file which show the effect of the change and he scripts to test it with GardenWorld.


Add more query method to MTable

Date: 2007-08-21

MTable has new method:

	public Query createQuery(String whereClause, String trxName) 
	{
		return new Query(this, whereClause, trxName);
	}

Jar file in 2pack/lib will be packed into Adempiere.jar

Starting from revision 3261 of trunk, any jar file that is pack of the lib folder of a 2pack package will be packed into the Adempiere.jar by the run_setup process. What this mean is, you just need to compile all libero specific code, pack it in a jar file and place it under the lib folder of the libero 2pack archive. Please look at the latest FAPack006.zip in the svn packages folder for an example.


Reorganization of Adempiere menu

<menu name="" >
  <menu name="Export Format" >
    <window name="Export Format" />
  </menu>
</menu>


Suggested menu:

<menu name="Quote-to-Invoice" >
  <menu name="Sales Orders" >
    <menu name="Rules" >
      <window name="" />
      ...
    </menu>
    <menu name="Transact" >
      <window name="" />
      ...
    </menu>
    <menu name="Report and Inquire" >
      <window name="" />
      ...
    </menu>
    <menu name="Processes" >
      <window name="" />
      ...
    </menu>
  </menu>

  <menu name="Shipments" >
    <menu name="Rules" >
      <window name="" />
      ...
    </menu>
    <menu name="Transact" >
      <window name="" />
      ...
    </menu>
    <menu name="Report and Inquire" >
      <window name="" />
      ...
    </menu>
    <menu name="Processes" >
      <window name="" />
      ...
    </menu>
  </menu>
</menu>



How to add chache suport for persistent class?

Add class variable to hold reference to Cache.

public class MActivity extends X_C_Activity
{
      /** Static Cache */
      private static CCache<Integer, MActivity> s_cache = new CCache<Integer, MActivity>(Table_Name, 30);

Change getXxxx method

      public static MActivity get(Properties ctx, int C_Activity_ID)
      {
              if (C_Activity_ID <= 0)
             {
                      return null;
              }
              // Try cache
              MActivity activity = s_cache.get(C_Activity_ID);
              if (activity != null)
              {
                      return activity;
              }
              // Load from DB
              activity = new MActivity(ctx, C_Activity_ID, null);
              if (activity.get_ID() == C_Activity_ID)
              {
                      s_cache.put(C_Activity_ID, activity);
              }
              else
              {
                      activity = null;
              }
              return activity;
      }

OpenXava Q & A

Created an entity ticketClass with a method GenerateTickets. Implemented the calculator using an IModelCalculator class.

Want to create a button/link when ticketClass is displayed so that user click on it the method gets executed.


Q.1 How do I set this up?

  • A1. Have to create an action and include it in the controller of your module.

In this action, you can obtain the object and call to you method, something like this:

public void execute() throws Exception {
  ...
  TicketClass ticketClass = (TicketClass) MapFacade.findEntity(getModelName(), getView().getKeyValues());
  ticketClass.generateTickets();
  getView().findObject(); // This refresh the view (Q2)
  ...
}


Q.2 How can I get the view of the entity refreshed after the method is executed.

  • A2. See the above code


Q.3 How can i reload other modules to reflect the changes made by an action that was performed in one module?

An action in one module (module A) that changes the value of the data contained in another module (module B), when i execute an action button (in module A), it executes successfully and updates the data in the other entity (entity B) in the database, but when i view module B, the module list view (of module B) is still showing the old data!

  • A3. In a Liferay 2.1.3 with OX2.2 it works fine, that is, I modify data in a portlet, I move to another and the data

is refreshed without touch filter nor any other action. Look at your web/WEB-INF/portlet.xml, have you <expiration-cache>0</expiration-cache> for your portlets? Since OX2.1 expiration-cache is 0 in the portlet.xml generated by OpenXava. Hence, if you are using OX2.1 or better you only need to execute the ant target 'redeployPortlets' in order to fix your portlet.xml file.


Q.4 How to avoid warnings that messages are not translated

  • A4.

This messages warn you about the labels that you need to put in your i18n/YourProyectName-labels_en.properties. Developer can avoid this messages putting the next line:

i18nWarnings=false

in the file properties/xava.properties of your project.


Q.5 Example TestCase

public class ExampleTest extends ModuleTestBase { 
 
  public DepartmentsTest(String testName) { 
  
  super(testName,"ExampleManager", "ExampleModlue");
  } 
  
  public void testCreateReadUpdateDelete() throws Exception { 
  
    //Test Create
    execute("CRUD.new");
    setValue("id", "ENG");
    setValue("name", "Example Name");
    setValue("description", "Example description");
    assertNoErrors();
  } 
}


Q.6 How to provide a web API for accessing the OpenXava application?

Just use Web Services. The OpenXava applications are standard Java Web applications, therefore you can use any Java API from your OpenXava application.

In this case you can create a simple Java class, with the services of you application that you want to expose. And use from it MapFacade or the model objects generated by OpenXava, or whatever code of your application you want. Something like this:

public class MyService {

  public Address getCustomerAddress(String customerId) throws Exception {
    // Here you use the POJOs generated by OpenXava
    Customer customer = (Customer) Customer.findById(customerId);
    return customer.getAddress();
  }
}

Now you can create a Web Service from MyService class using AXIS, or the wizard included in Eclipse WTP. But, remember, all this is a Java issue, not an OpenXava one.


Q.7 Magic numbers

It is possible to define some magic numbers in OX? The Environment Variables are not enough. I want to use the magic numbers in components.

  • A7. Use standard techniques for Java and XML.

In Java you can use "public final static" variables:

public class MyMagicNumbers {
  public final static int MY_MAGIC = 24324;
}

In XML you can use XML ENTITIES:

<!ENTITY mymagic "24324">

and then use &mymagic; instead of 34324.

These techniques are not from OpenXava but from Java and XML.

In XML you can use @mymagic@, and the filter of OpenXava ant build changes it from a value of your properties file. This allows you to have a different value of @mymagic@ for each customer or configuration. This is a technique from OpenXava.

Hint for Defining Oracle View

* [sf.net post http://adempiere.svn.sourceforge.net/adempiere/?rev=11270&view=rev]
-'CO',  --mrp.docstatus,
+CAST('CO' AS nvarchar2(2)),  --mrp.docstatus,

Q.8 How to access EJB from OpenXava

  • Reference Guide section 3.14.
  • There is a example of using a EJB inside validator.
  • Developer can access EJB code in any part of OpenXava applications, for example:
Delivery delivery = DeliveryUtil.getHome().findByNumber(33);
delivery.generateInvoice();


Q.9 Access rights depending on the user profile

  • sf.net thread
  • User should only see fields or to execute actions depending on his user profile (or role).
  • Certain users to be able to update certain fields while others may only read them.


1. Option - provided by the container portal

  • Create two groups of users:
    • First with create-delete-edit rights.
    • Second only with read rights.
  • Define two modules(portlets):
    • First with CRUD controller.
    • Second with Print controller.

The admin may assign First or Second module (portlet) for each group of users with the portal administrative tools.

2. Option

Not completly implemented in OpenXava, but there are some useful tools in OX that can help:

  • Users.getCurrent() return the current user if application is started in portal. This value may be used in any point of the code.
  • Filters: filters the data displayed in the mode list according to the current user.

A full management of users in OX project implies creation of several modules for storing the info about users, interfaces for change rights and create new users, roles and rights, overwrite standard controllers, to write new actions (typically "save" action, frequently "new" action too), to write specific validator, to write filters for tabs in list mode, and more...

Javier's opinion: I think that part of this stuff can be moved to a new project, instead of be included in OX core. This new project would manage access rights and user profiles, and will be reusable for other projects.

Links


Q.10 Property displayed as Radio button

  • Existing test case: CustomerWithRadioButtons
  • Section 4.7 of reference guide shows how editors are full configurable in OpenXava.
  • /components/SomeComponent.xml file
<entity>
  <property name="gender" >
	<valid-values>
		<valid-value value="Male"/>
		<valid-value value="Female"/>
	</valid-values>
  </property>
</entity>
<view>
  <property-view property="gender" editor="ValidValuesRadioButton"></property-view>
  <members >
    name;
    gender;
  </members >
<view>
  • OpenXava/xava/default-editors.xml contains definition of:
    • ValidValuesRadioButton
<editor name="ValidValuesRadioButton" url="radioButtonEditor.jsp">
	<property name="horizontal" value="true" />
</editor>
    • ValidValuesVerticalRadioButton
<editor name="ValidValuesVerticalRadioButton"
	url="radioButtonEditor.jsp">
	<property name="horizontal" value="true" />
</editor>
    • ValidValuesHorizontalRadioButton
<editor name="ValidValuesHorizontalRadioButton"
	url="radioButtonEditor.jsp">
	<property name="horizontal" value="true" />
</editor>

It looks both last have the same value for horizontal request parameter <property name="horizontal" value="true" />. Probably this is a mistake.


Q.11 OpenXava design

  • All model classes implement org.openxava.model.IModel interface:
/**
 * Interface to be implemented by all model classes. <p>
 * 
 * The model classes may be EntityBeans EJB 2 or POJOs (for JDO, EJB3 or Hibernate).
 * 
 * @author Javier Paniza
 */
public interface IModel {
	
	/**
	 * Returns metadata about object. <p>
	 * 
	 * @return  Not null.
	 * @exception XavaException  Any problem related to OpenXava.
	 * @exception RemoteException  System problem.
	 */
	MetaModel getMetaModel() throws XavaException, RemoteException;

}


Q.12 How to get logger

OpenXava uses Apache Commons Loggin

private static Log log = LogFactory.getLog(Users.class);


Q.13 Using the <transient> tag

Section 4.10 of Reference guide says:

"put <transient/> at the end of the component definition (1), just in the part for the mappings"

Make sure you write the following:


<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE component SYSTEM "dtds/component.dtd">
<component name="YourComponent">
     <entity>
         ...
     </entity>
     ...
     <transient/>   <!--If you put it anywhere else you will see error in the
document-->
</component>


Q.14 Search criteria customization in List mode

Requirement: The user writes his search criteria (Type employee salary for example) and then presses the search button.

Create a module that start in detail mode. Use a view that show just the data for filtering.

Define a controller with your "Search" actions, and in this action change to list mode.

In this way you can achieve your goal.

Read the chapters 7 and 8 of Reference Guide.


Q.15 How to change Action view from link to button?

1. Create a new property called 'buttonsForNoImageActions' (or so), for use in xava.properties.

2. Add the property in the class XavaPreferences.

3. Modify the class ActionTag.

By default, buttonsForNoImageActions must be 'false', in order to not change the current OX behavior (by default).


Temporary solution. Change actionEditor.jsp

- <xava:link action="<%=p.getAction()%>"/> 
+ <xava:button action="<%=p.getAction()%>"/> 


Q.16 How to open/goto custom jsp?

DECLARATIVE jsp navigation, as following:

<action name="goToMyJSP" class="org.openxava.actions.NavigationAction">				
  <set property="customView" value="mypage.jsp"/>
  <set property="nextController" value="MyController"/>
</action>				

In general, action classes can be made reusable, and can be configured in controllers.xml. In this way can do more configuration and less programming.


Q.17 How to make a property as a NOT persistent or view only field in the entity element?

Yes, it's possible.

Two options:

Option 1. Calculated properties (section 3.8.4 of reference guide)

It is possible to use qualified properties!

<component name="Engagement">
	<entity>
...
<property name="cmpContactName" type="String" size="60" >
	<calculator class="org.openxava.calculators.StringCalculator" > 
		<set property="string" from="cmpContact.compName.custCompName"/> 
	</calculator>
</property>
...
</component>

No need to create special Calculator!

<component name="Engagement">
	<entity>
...
<property name="cmpContactName" type="String" size="60" >
	<calculator class="bg.d3soft.openxava.calculators.CompanyContactNameCalculator" >
	</calculator>
</property-->
...
</component>


Option 2. View properties (section 4.5 of reference guide)

View properties are used for the case that you want to have something editable in the view, but you do not want to save it in DB. View properties do not have same functionality as properties. That's why this behavior is normal. For some reason view property do not show the same result:

<component>
...
<view>
	<property name="cmpContactName" type="String" size="60" >
		<calculator class="org.openxava.calculators.StringCalculator" > 
			<set property="string" from="cmpContact.compName.custCompName"/> 
		</calculator>
	</property>
...
</view>
</component>


Q.18 How to implement Wizard form

First consider seriously to use a single view with several sections. This is more flexible in most cases and easy to implement with OpenXava.

Steps: Your action for navigate must implements IChangeControllersAction, this allows you to change the actions to show in each page, and using

getView().setViewName( ... );

inside execute() of your action, you can change the view in each step.

Look at the Reference Guide and OpenXavaTest for IChangeControllersAction, and also for INavigationAction.


Q.19 How to forward to a JSP

Action obtains two values which are important for the JSP.

Create a IForwardAction with a code like:

 
public String getForwardURI() { 
    return "/mypage.jsp?a=" + getA() + "&b=" + getB(); 
}

And put mypage.jsp in the 'web' folder of your project.


Q.20 How to change order of error messages?

Method 'validate' of MapFacadeBean:

private void validate(Messages errors, MetaModel metaModel, Map values, Map
keyValues, Object containerKey, boolean creating)
		throws XavaException, RemoteException {		
		Iterator it = values.entrySet().iterator();		
		while (it.hasNext()) {
			Map.Entry en = (Map.Entry) it.next();
			String name = (String) en.getKey();
			Object value = en.getValue();
			validate(errors, metaModel, name, value, creating);
		}
		if (metaModel.containsValidadors()) {
			validateWithModelValidator(errors, metaModel, values, keyValues,
 containerKey,
creating);
		}
	}

In this case the loop is over entrySet of a Map, hence the order is unknown. You can modify this method in order to work by the correct order, some method of MetaModel returns the member names in order of declaration.

Q.21 How to disable the icon in the corner most column in column arrangement view?

Modify list.jsp.


Q.22 How to open custom JSP view in a pop up window?

Is it possible to open custom JSP view in a pop up window?

No. The custom JSP must be inside the module. Use IForwardAction that has a method (inNewWindow()) for create a pop up windows. You can put your JSP in a public folder, and send the parameters for this jsp using OX session objects.

Q.23 How to hide/show view section depending on user rights?

Some users must be able to see given section from the view while other users must NOT be able to see this section.

At the moment developer can hide (using View.setHidden) members and groups, but not sections.

There are 4 (at least) options:

  • Use OpenXava/src/org.openxava.view.View.setSectionEditable.
  • Put your sensible data in a group, and hide/show it.
  • Create 2 views (one with the critical section, another without it).
  • Modify OpenXava to allow to hide programatically sections.


  • Which class should change view or hide section?
    • SEARCH_ACTION and 'new' Action are good places.
    • Other options as in an on-each-request action, or an on-init action.


  • Guide how to develop option 4?
    • Look at OpenXava/src/org.openxava.view.View.setHidden method.

Q.24 How to display only label in view?

Use a property of the view with stereotype LABEL.

<view>
  <property name="selectLabel" stereotype="LABEL" type="String" >
  </property>
  <members>
     selectLabel;
     a;
     b;
  </members>
</view>

File Application_en.properties must contain translation of property:

selectLabel=Select at least one:


Another option may be to have a property with no content and 'Select at least one:' as label.

File: xava/editors.xml

<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE editors SYSTEM "dtds/editors.dtd">
<editors>
	<editor url="void.jsp">
		<for-stereotype stereotype="VOID" />
	</editor>
</editors>

Create empty file: web/xava/editors/void.jsp


Difference is only in stereotype:

<view>
  <property name="selectLabel" stereotype="VOID" type="String">
  </property>
  <members>
     selectLabel;
     a;
     b;
  </members>
</view>


Q.25 How to make property required from Action?

Make a property 'X' required depending on the value of other property 'A'. If property 'A' has value 111 than property 'X' must became required.

<property-view property="A">
    <on-change class="bg.d3soft.openxava.actions.OnChangeA"/> 
</property-view>
  • Use a validator for property X (reference guide 3.8.6), and inject in the validator the property A (using <set />).
  • Use a model level validator (section 3.16).

You cannot change the state of a property from 'required' to 'not required' at runtime (well, you can, but you shouldn't), because required is a feature of the model, not of a view. You can access to the MetaModel at runtime and change the value of required, but if you do it in this way the value will be change for all modules, and all users at the same time, because you are modifying the meta data of the model.


Q.26 How to display property of a reference?

View properties are used for the case that you want to have something editable in the view, but you do not want to save it in DB.

  • If property must be read only than use a calculated property.
  • If property must be editable than:

Use a property of the view, populate it in the code in SEARCH_ACTION, and save its content in your own Save action.


Q.27 How to add EMAIL stereotype and validation class

Will be part of OpenXava 2.2.3! sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="EMAIL" size="50"/>


2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp">
  <for-stereotype stereotype="EMAIL"/>
</editor>


3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="EMAIL" type="String"/>


4) Add to Application/i18n/messages_en.properties file

email_validation_error={0} must be a valid email address


5) Validation class

package org.openxava.validators;

import org.openxava.util.*;
import org.openxava.validators.IPropertyValidator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class EmailValidator implements IPropertyValidator {

    Pattern pat=Pattern.compile(".+@.+\\.[a-z]+");

    public void validate(Messages errors, Object value, String propertyName,
String modelName) throws Exception {

        if (value == null || value.toString().length() ==0 ) return;
        Matcher matcher = pat.matcher(value.toString());
        if (! matcher.find()) {
           errors.add("email_validation_error", propertyName);
           return;
        }
    }
}


5.1) Second version of Validator class using Apache Commons validator

import org.apache.commons.validator.GenericValidator;
import org.openxava.util.Messages;
import org.openxava.validators.IPropertyValidator;

/**
 * @author Janesh Kodikara
 */
public class EmailValidator implements IPropertyValidator {

    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {

        if (value == null || value.toString().length() == 0) 
          return;

        if (! GenericValidator.isEmail(value.toString())) {
            errors.add("email_validation_error", propertyName);
            return;
        }
    }
}


Q.28 How to translate OpenXava

For example to Bulgarian (bg).

  • Copy OpenXava/i18n/Labels_en.properties to OpenXava/i18n/Labels_bg.properties.
  • Copy OpenXava/i18n/Messages_en.properties to OpenXava/i18n/Messages_bg.properties.

Translate Labels_bg.properties and Messages_bg.properties!

Thread which can be useful: here


Q.29 How to translate application based on OpenXava

For example to Bulgarian (bg).

  • Copy <ApplictionName>/i18n/Labels_en.properties to <ApplictionName>/i18n/Labels_bg.properties.
  • Copy <ApplictionName>/i18n/Messages_en.properties to <ApplictionName>/i18n/Messages_bg.properties.

Translate Labels_bg.properties and Messages_bg.properties!

Thread which can be useful: here


Q.30 How to add PASSWORD stereotype

1) Create new file in Application/web/xava/editors/passwordEditor.jsp

<%@ page import="org.openxava.model.meta.MetaProperty" %>
<%@ page import="org.openxava.util.Strings" %>
<%@ page import="org.openxava.util.Align" %>

<jsp:useBean id="style" class="org.openxava.web.style.Style" scope="request"/>

<%
String propertyKey = request.getParameter("propertyKey");
MetaProperty p = (MetaProperty) request.getAttribute(propertyKey);
String fvalue = (String) request.getAttribute(propertyKey + ".fvalue");
String align = p.isNumber()?"style='text-align:right'":"";
boolean editable="true".equals(request.getParameter("editable"));
String disabled=editable?"":"disabled";
String script = request.getParameter("script");
boolean label = org.openxava.util.XavaPreferences.getInstance().isReadOnlyAsLabel();
String smaxSize = request.getParameter("maxSize");
int maxSize = 0;
if (!org.openxava.util.Is.emptyString(smaxSize)) {
	maxSize = Integer.parseInt(smaxSize);
}
else {
	maxSize = org.openxava.util.XavaPreferences.getInstance().getMaxSizeForTextEditor();
}
int size = p.getSize() > maxSize ? maxSize : p.getSize(); 

/*
boolean fillWithZeros = "true".equals(request.getParameter("fillWithZeros"));
if (fillWithZeros && fvalue.length() > 0) {	
	fvalue = Strings.fix(fvalue, size, Align.RIGHT, '0');
}
*/

if (editable || !label) {
%>
<input name="<%=propertyKey%>" class=<%=style.getEditor()%>
	type="password" 
	title="<%=p.getDescription(request)%>"
	<%=align%>
	maxlength="<%=p.getSize()%>" 
	size="<%=size%>" 
	value="<%=Strings.change(fvalue, "\"", """)%>"
	<%=disabled%>
	<%=script%>
	/>
<%
} else {
%>
<%=fvalue%> 
<%
}
%>
<% if (!editable) { %>
	<input type="password" name="<%=propertyKey%>" value="<%=fvalue%>">
<% } %>			


2) Add to Application/xava/editors.xml

<editor url="passwordEditor.jsp">
  <for-stereotype stereotype="PASSWORD"/>
</editor>


3) Add to Application/xava/Stereotype-type-default.xml

<for stereotype="PASSWORD" type="String"/>


4) Example usage Application/components/Employee.xml

<component name="Employee">
  <entity>
...
    <property name="empPassword" stereotype="PASSWORD" size="20" required="true"/>
...


Q.31 How to develop import functionality

1) Create new file in Application/web/xava/editors/loadCSVFile.jsp

<%@ include file="../imports.jsp"%>

<jsp:useBean id="style" class="org.openxava.web.style.Style" scope="request"/>

<table>
<th align='left' class="<%=style.getLabel()%>">
<xava:message key="enter_new_csv_file"/>
</th>
<td>
<input name = "newCsvFile" class=<%=style.getEditor()%> type="file" size='60'/>
</td>
</table>


2) Add new class to Application/src/bg/d3soft/openxava/actions/LoadCSVFileAction.java

package bg.d3soft.openxava.actions;

import org.openxava.actions.BaseAction;
import org.openxava.actions.ILoadFileAction;

public class LoadCSVFileAction extends BaseAction implements ILoadFileAction {
	
	private String newCsvFileProperty;	
	
	
	public void execute() throws Exception {
		
	}
	
	public String[] getNextControllers() {
		return new String [] { "ImportCSVFile" };
	}

	public String getCustomView() {
		return "xava/editors/loadCSVFile";
	}

	public boolean isLoadFile() {
		return true;
	}

	public String getNewCsvFileProperty() {
		return newCsvFileProperty;
	}

	public void setewCsvFileProperty(String string) {
		newCsvFileProperty = string;
	}
}


3) Add new class to Application/src/bg/d3soft/openxava/actions/ImportCsvFileAction.java

package bg.d3soft.openxava.actions;

public class ImportCsvFileAction extends BaseAction implements INavigationAction, IProcessLoadedFileAction {
	
	private static Log log = LogFactory.getLog(ImportCsvFileAction.class);
	
	private List fileItems;
	
	private View view;
	
	private String newCsvFileProperty;
	
	public void execute() throws Exception {		
		Iterator i = getFileItems().iterator();
		while (i.hasNext()) {
			FileItem fi = (FileItem)i.next();
			String fileName = fi.getName();
			log.info("fileName = [" + fileName + "]");
			
			if (!Is.emptyString(fileName)) {
				//getView().setValue(getNewCsvFileProperty(), fi.get());
				log.info("getNewCsvFileProperty = [" + getNewCsvFileProperty() + "]");
				log.info("fi.getSize() = [" + fi.getSize() + "]");
				
				ImportUtil impUtil = new ImportUtil();
				impUtil.importCustomerCompanyFromCSV(fi);
			}			
		}		
	}

	public String[] getNextControllers() {		
		return DEFAULT_CONTROLLERS;
	}
	public String getCustomView() {		
		return DEFAULT_VIEW;
	}
	public View getView() {
		return view;
	}
	public void setView(View view) {
		this.view = view;
	}
	public String getNewCsvFileProperty() {
		return newCsvFileProperty;
	}
	public void setNewCsvFileProperty(String string) {
		newCsvFileProperty = string;	
	}
	public List getFileItems() {
		return fileItems;
	}
	public void setFileItems(List fileItems) {
		this.fileItems = fileItems;
	}
}


4) Add to Application/i18n/Application-labels_en.properties

loadCSVFile=Import CSV File
importCSVFile=Import CSV File


5) Add to Application/i18n/Application-messages_en.properties

enter_new_csv_file=Enter CSV File


6) Add new class to Application/src/bg/d3soft/openxava/actions/LoadCSVFileAction.java



7) Add to Application/xava/controllers.xml

<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE controllers SYSTEM "dtds/controllers.dtd">
<controllers> 
...	
	<object name="xava_newCsvFileProperty" class="java.lang.String"/>
...
	<controller name="LoadCSVFile">
		<action name="loadCSVFile"
			class="bg.d3soft.openxava.actions.LoadCSVFileAction"
			hidden="false"
			mode="detail"
			image="images/db_update.png"
		>
			<!-- set value="" property=""/-->
			<!-- use-object name="xava_newCsvFileProperty"/-->
		</action>
		
	</controller>
	
	<controller name="ImportCSVFile">
		<action name="importCSVFile"
			class="bg.d3soft.openxava.actions.ImportCsvFileAction"
			hidden="false"
			mode="detail"
		>
			<use-object name="xava_view"/>
			<use-object name="xava_newCsvFileProperty"/>
		</action>
		
		<action name="cancel"
			class="org.openxava.actions.CancelAction">			
		</action>
	</controller>
...
</controllers>


8) Add to Application/web/xava/images/db_update.png


9) Example usage Application/xava/application.xml

...
	<module name="CustomerCompanies">
		<model name="CustomerCompany" />
		<controller name="TypicalNotResetOnSave" />
		<controller name="LoadCSVFile" />
	</module>
...


10) Example csv file

"Date Created","Name","Web Address","Corp Location"
"ERROR","CustCompName-5","www.web005.com","Corp. location-5"
"ERROR","CustCompName-6","www.web006.com","Corp. location-6"
"ERROR","CustCompName-7","www.web007.com","Corp. Location-7"


Q.32 How to create formatter for java.util.Date

Option A - Create your own formatter (IFormatter) =

Create your own formatter (IFormatter) and assign it to the java.util.Date type in editors.xml of your project.

OpenXava version 2.2.2 OpenXavaTest/xava/editors

<editor name="DateCalendar" url="dateCalendarEditor.jsp"> 
  <formatter class="org.openxava.formatters.DateFormatter" /> 
  <for-type type="java.util.Date" /> 
</editor>


Option B - Modify DateFormatter (and DateTimeFormatter) of OpenXava

Look at DateFormatter (and DateTimeFormatter) of OpenXava. They are tuned for work better for Spanish and Polish.

Changes in the DateFormatter and in the DateTimeFormatter.

In DateFormater added:

private static DateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); // modified
private static DateFormat spanishDateFormat = new SimpleDateFormat("dd/MM/yyyy");
	
private static DateFormat [] spanishDateFormats = {
	spanishDateFormat,
	germanDateFormat, // modified
	new SimpleDateFormat("ddMMyyyy"),
	new SimpleDateFormat("dd.MM.yyyy"),		
};	

In method getDateFormat(HttpServletRequest request)

if ("de".equals(request.getLocale().getLanguage())) return germanDateFormat; // modified

In DateTimeFormatter added:

private static DateFormat germanDateFormat = new  SimpleDateFormat("dd.MM.yyyy"); //modified

In the method getDateFormat(HttpServletRequest request)

if ("de".equals(request.getLocale().getLanguage())) return germanDateFormat; // modified


full source code of Formatter:

import java.text.*;

import javax.servlet.http.*;

import org.openxava.util.*;
import org.openxava.formatters.*;

public class GermanDateFormatter implements IFormatter {
	
	private static DateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
	private static DateFormat spanishDateFormat = new SimpleDateFormat("dd/MM/yyyy");
	
	private static DateFormat [] spanishDateFormats = {
		spanishDateFormat,
		germanDateFormat,   
		new SimpleDateFormat("ddMMyyyy"),
		new SimpleDateFormat("dd.MM.yyyy"),		
	};	
	
	public String format(HttpServletRequest request, Object date) {
		if (date == null) return "";
		if (Dates.getYear((java.util.Date)date) < 2) return "";
		return getDateFormat(request).format(date);
	}
		
	public Object parse(HttpServletRequest request, String string) throws ParseException
{
		if (Is.emptyString(string)) return null;
		if (string.indexOf('-') >= 0) { // SimpleDateFormat does not work well with -
			string = Strings.change(string, "-", "/");
		}		
		DateFormat [] dateFormats = getDateFormats(request); 
		for (int i=0; i<dateFormats.length; i++) {
			try {
				return dateFormats[i].parseObject(string);
			}
			catch (ParseException ex) {
			}						
		}
		throw new ParseException(XavaResources.getString("bad_date_format",string),-1);
	}
	
	private DateFormat getDateFormat(HttpServletRequest request) {
		if ("de".equals(request.getLocale().getLanguage())) 
                  return germanDateFormat;
		if ("es".equals(request.getLocale().getLanguage()) ||
		    "pl".equals(request.getLocale().getLanguage())
                ) 
                  return spanishDateFormat;
		return DateFormat.getDateInstance(DateFormat.SHORT, request.getLocale());		
	}
	
	private DateFormat[] getDateFormats(HttpServletRequest request) {
		if ("es".equals(request.getLocale().getLanguage()) ||
                    "de".equals(request.getLocale().getLanguage()) ||
		    "pl".equals(request.getLocale().getLanguage())
                ) 
                  return spanishDateFormats;
		return new DateFormat [] { getDateFormat(request) };
	}
}


Q.33 Possible properties in Application/properties/xava.properties

Java class responsible for loading xava.properties: org.openxava.util.XavaPreferences

Since version > 2.2.2.

User can hide/show the filter part on init in list and collections.

showFilterByDefaultInList=true
showFilterByDefaultInCollections=true


Since version 2.2.1.

Users.getCurrent() returns the user email instead of the user id inside a portal.

emailAsUserNameInPortal=true|false(false)
readOnlyAsLabel=true|false(false)
tabAsEJB=true|false(false)
showCountInList=true|false(true)  - avoid the initial select count(*) in the model list. It takes time when number of rows is big.
formLineSpacing=integer(1)
csvSeparator=character(;)
persistenceProviderClass=String(org.openxava.model.impl.HibernatePersistenceProvider)org.openxava.model.impl.EJBPersistenceProvider
mapFacadeAsEJB=true|false(false)
detailOnBottomInCollections=true|false(false)
jpaCodeInPOJOs=
i18nWarnings=true|false(true)
duplicateComponentWarnings=true|false(true)
failOnAnnotationMisuse=true|false(true)
maxSizeForTextEditorinteger(100)
javaLoggingLevel=String(INFO);FINEST|INFO
hibernateJavaLoggingLevel=String(INFO);FINEST|INFO

How to access Xava Properties from jsp file?

<%
boolean label = org.openxava.util.XavaPreferences.getInstance().isReadOnlyAsLabel();
%>


Q.34 How to add WEBURL stereotype and validation class

Will be part of OpenXava 2.2.3! sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="WEBURL" size="50"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp">
  <for-stereotype stereotype="WEBURL"/>
</editor>

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="WEBURL" type="String"/>

4) Add to Application/i18n/messages_en.properties file

url_validation_error={0} must be a valid url

5) Validation class

import org.openxava.util.*;
import org.openxava.validators.IPropertyValidator;
import org.apache.commons.validator.UrlValidator;

/**
 * @author Janesh Kodikara
 */
public class URLValidator implements IPropertyValidator {

    UrlValidator urlValidator = new UrlValidator();

    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {

        if (value == null || value.toString().length() == 0 ) 
          return;

        if ( !urlValidator.isValid(value.toString()) ) {
          errors.add("url_validation_error", propertyName);
        }
    }
}

6) Add following jar files

A) jakarta-oro-2.0.8.jar http://jakarta.apache.org/site/downloads/downloads_oro.cgi?Preferred=http%3A%2F%2Fgovernment-grants.org%2Fmirrors%2Fapache.org

B) commons-validator-1.3.1.jar http://commons.apache.org/downloads/download_validator.cgi


Q.35 How to add TELEPHONE stereotype and validation class

Will be part of OpenXava 2.2.3! sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="TELEPHONE" size="15"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp">
  <for-stereotype stereotype="TELEPHONE"/>
</editor>

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="TELEPHONE" type="String"/>

4) Add to Application/i18n/messages_en.properties file

phone_valid_number_error= {0} must be a valid number
phone_minimum_size_error= {0} must be at least  {2} {1} long

5) Validation class

package org.openxava.validators;

import org.openxava.util.*;
import org.openxava.validators.IPropertyValidator;

/**
 * @author Janesh Kodikara
 */
public class PhoneNumberValidator implements IPropertyValidator {
    private long minSize = 8;
    private Long phoneNumber;

    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {

        if (value == null || value.toString().length() ==0 ) 
          return;

        //Check if input is a number
        try {
          phoneNumber = new  Long(value.toString());
        } catch(NumberFormatException ex){
           errors.add("phone_valid_number_error", propertyName);
           return;
        }

        //Check if input length is at least greater than specified minimum length
        if ( phoneNumber.toString().length() < minSize) {
            errors.add("phone_minimum_size_error", propertyName, "digits", new Long(minSize));
            return;
        }
    }
    public long getMinSize() {
        return minSize;
    }
    public void setMinSize(long minSize) {
        this.minSize= minSize;
    }
}


Q.36 How to hide field/property

Section 3.8 of Reference Guide:

A hidden property has a meaning for the developer but not for the user.
The hidden properties are excluded when the automatic user interface is generated. 
However at Java code level they are present and fully functional. 
Even if you put it explicitly into a view the property will be shown in the user interface.

Hidden property is used mainly for hidden keys, but if you put the property in the view, the property will be show.

Example code:

When the user click 'Others' from languages combo box, the Others text field should be displayed. Entity part:

<!-- The Combo box -->
<property name="languages">
  <valid-values>
    ....
    <valid-value value="English"/>
    <valid-value value="Others"/>
  </valid-values> 
</property> 

The Others property:

<property name="otherLanguages" hidden="true" type="String" size="20"/>

View part:

<property-view property="languages"> 
  <on-change class="com.sami.actions.OnLanguagesChange"/> 
</property-view>

'OnLanguagesChange' action:

...
getView().setHidden("otherLanguages", false);
...

The action is perfect, when user choose the Others from the combo box, the Others text field displayed, but the problem is, that Others text field displayed when user request the form always (On the initial view).

Can not hide properties in the initial view, when user click any action the hidden properties disappear?!

The solution in this case is to refine the 'new' action and the 'search' action to hide the 'otherLanguage' property. But, before, try to define your module in this way:

<module name="YourModule"> 
  <env-var name="XAVA_SEARCH_ACTION" value="CRUD.searchExecutingOnChange"/> 
... 
</module> 

Look at section 7.6 of reference guide !


Q.37 How to set rows count in tab


Q.38 How to separate translations for each portlet

Source file which handle this: org.openxava.util.Labels

For properties in tabs, in i18n file (assuming there are ComponentFoo and ComponentBar, both with references to Example Component)

ComponentFoo.tab.properties.example.description=Example foo 
ComponentBar.tab.properties.example.description=Example bar 

For properties in detail mode:

ComnponentFoo.view.level=Level Foo 
ComnponentBar.view.level=Level Bar 

For properties which are part of view:

# CustomerContact tab inside Engagement
CustomerContact.views.CustomerContact.custName=Customer Name
CustomerContact.views.CustomerContact.custName[description]=Customer Name
CustomerContact.views.<View Name Here>.custName[description]=Customer Name

CustomerContact.tab.properties.custName=Customer Name
CustomerContact.tab.properties.custName[description]=Customer Name


Q.39 How to create custom JasperReport which uses JDBC connection to DB

Need returning null in the method getDataSource() of your JasperReport action (derived from JasperReportBaseAction).

public class EngagementReportAction extends JasperReportBaseAction {
...
protected JRDataSource getDataSource() throws Exception {
  return null;
}
...
}


Q.40 Since version 2.2.1 Calendar overlaps with Main Horizontal line of Portal

In order to change this behavior modify file OpenXava/web/script.jsp Old line:

97:   _dynarch_popupCalendar.showAtElement(el.nextSibling, "Br");        // show the calendar

New line

97:   _dynarch_popupCalendar.showAtElement(el.nextSibling, "tr");        // show the calendar

"tr" is need for IE6, for working fine when date fields are at the bottom of the page. Will be available in OX2.2.3 Correct one is:

  <% 
  // Date fields at end of the windows is not shown correctly in IE6,
  // hence, we align at top in IE6, and at botton in other browsers.
  String browser = request.getHeader("user-agent");
  String calendarAlign = browser != null && browser.indexOf("MSIE 6") >= 0 ? "tr" : "Br";  
  %>
  _dynarch_popupCalendar.showAtElement(el.nextSibling, "<%=calendarAlign%>"); // show the calendar


Q.41 How to filter by property of Reference?

All fields by which developer wants to make filtering MUST be described in 'properties' tag!

Example code:

<tab>
	<filter class="bg.d3soft.openxava.filters.UserFilter"/>
	<properties>custCrtdBy, engDealMkr.empLogonID, rptProMng.empLogonID</properties>
	<base-condition>${custCrtdBy} = ? or ${engDealMkr.empLogonID} = ? or ${rptProMng.empLogonID} = ?</base-condition>
	...
</tab>


Q.42 How to display confirmation message?

Confirmation message can be shown using the 'confirm' attribute of <action /> element in controllers.xml. Section 7.3 of reference guide.

This is a generic feature that can be applied to any action that show a JavaScript dialog to confirm the action. Given that it's generic (not only for list mode), it cannot revise if some item is selected (or any other condition). It simply ask before action to be execute.

A solution can be to define your list action using confirm=false, and then create your logic that move to other controller for confirmation before execute your definitive action, in this way you can control exactly your logic, but you will lost the agility of using a JavaScript dialog.


Q.43 Openxava and Cross Site Scripting (XSS)?


Q.44 How to use Identity Column?

Identity must be declared in DB as an auto increment column.

Hibernate will create proper DB script. When updateSchema Ant target is executed( on OpenXava project which uses hibernate), it will generates the correct SQL script. For example, if you are using AS/400 dialect, hibernate will generate a line like:

ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY

If in your OX componentes you write:

<property name="number" type="Integer" key="true" size="5" required="false">
	<default-value-calculator class="org.openxava.calculators.IdentityCalculator"
on-create="true"/>
</property>

OpenXava generates a .hbm.xml with the next content:

<id name="number" column="NUMBER" access="field" type='java.lang.Integer'
 length='5'>
	<generator class='identity'/>
</id>


Q.45 How to add IP stereotype and validation class

Will be part of OpenXava 2.2.4!

sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="IP" size="15"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp"> 
  <for-stereotype stereotype="IP"/> 
</editor> 

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="IP" type="String"/> 

4) Add to Application/i18n/messages_en.properties file

ip_validation_error={0} must be a valid IP address

5) Validation class

package org.openxava.validators;

import org.openxava.validators.IPropertyValidator;
import org.openxava.util.Messages;
import org.apache.commons.validator.GenericValidator;

/**
 * @author Janesh Kodikara
 */
public class IPValidator implements IPropertyValidator {

    public void validate(Messages errors, Object value, String propertyName,
String modelName) throws Exception 
{

        String numberRegExp = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
        String ipRegExp = "\\b" + numberRegExp + "\\." + numberRegExp + "\\." +  numberRegExp +"\\." + numberRegExp + "\\b";

        if (value == null || value.toString().length() == 0) return;

        if (! GenericValidator.matchRegexp(value.toString(), ipRegExp)) {
            errors.add("invalid_ip_error", propertyName);
        }
    }
}


Q.46 How to add Reachable IP stereotype and validation class

Will be part of OpenXava 2.2.4 and 3.0beta5!

sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="IP" size="15"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp"> 
  <for-stereotype stereotype="IP"/> 
</editor> 

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="IP" type="String"/> 

4) Add to Application/i18n/messages_en.properties file

ip_not_reachable={0} is Not Reachable

5) Validation class

package org.openxava.validators;

import org.openxava.validators.IPropertyValidator;
import org.openxava.util.Messages;
import org.apache.commons.validator.GenericValidator;

/**
 * @author Janesh Kodikara
 */
public class IPValidator implements IPropertyValidator {

    public void validate(Messages errors, Object value, String propertyName,
String modelName) throws Exception 
{

        if (value == null || value.toString().length() == 0) return;

        /*String address = "192.168.0.60";
        InetAddress addr = InetAddress.getByName( value );
        boolean isValidAddress = addr.isReachable(myTimeout);
        System.out.println(" Reachable " + isValidAddress);*/
        if (! GenericValidator.matchRegexp(value.toString(), ipRegExp)) {
            errors.add("ip_not_reachable", propertyName);
        }
    }
}

Q.47 How to avoid Warnings during Generate Portlet ant task?

Will be included in OX2.2.4!

Go to OpenXava/build.xml, and examine the target "generatePortletXml" ant task. It looks like:

<classpath> 
	<pathelement path="../OpenXava/bin"/>
	<fileset dir="../OpenXava/web/WEB-INF/lib"> 
		<include name="**/*.jar"/> 
	</fileset> 
	<fileset dir="web/WEB-INF/lib"> 
		<include name="**/*.jar"/>		
	</fileset>
...
</classpath>

Because we include ../OpenXava/bin and web/WEB-INF/lib that contains openxava.jar, the default-controllers.xml is duplicated in the path, then you get the warning.

Solution is just to exclude the openxava.jar from the classpath. Modify the build.xml of your OpenXava project.

<classpath> 
	<pathelement path="../OpenXava/bin"/>
	<fileset dir="../OpenXava/web/WEB-INF/lib"> 
		<include name="**/*.jar"/> 
	</fileset> 
	<fileset dir="web/WEB-INF/lib"> 
		<include name="**/*.jar"/>
		<exclude name="openxava.jar"/>
	</fileset>
...
</classpath>


Q.48 How to add ISBN stereotype and validation class

Will be part of OpenXava 2.2.4 and 3.0beta5!

sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="ISBN" size="13"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp"> 
  <for-stereotype stereotype="ISBN"/> 
</editor> 

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="ISBN" type="String"/> 

4) Add to Application/i18n/messages_en.properties file

isbn_validation_error={0} must be a valid ISBN address

5) Validation class

package org.openxava.validators; 
 
import org.openxava.validators.IPropertyValidator;
import org.openxava.util.Messages;
/**
 *
 * @author Janesh Kodikara
 */

public class ISBNValidator implements IPropertyValidator {

    private  org.apache.commons.validator.ISBNValidator validator = new org.apache.commons.validator.ISBNValidator();

    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {

        if (value == null || value.toString().length() ==0 ) return;

        if (! validator.isValid(value.toString())) {
           errors.add("isbn_validation_error", propertyName);
        }
    }

}

Q.49 How to add Credit Card stereotype and validation class

Will be part of OpenXava 2.2.4 and 3.0beta5!

sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="CREDIT_CARD" size="19"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp"> 
  <for-stereotype stereotype="CREDIT_CARD"/> 
</editor> 

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="CREDIT_CARD" type="String"/> 

4) Add to Application/i18n/messages_en.properties file

creditcard_validation_error={0} must be a valid Credit card number

5) Validation class

package org.openxava.validators;

import org.openxava.validators.IPropertyValidator;
import org.openxava.util.Messages;
import org.apache.commons.validator.GenericValidator;

/**
 * @author Janesh Kodikara
 */
public class CreditCardValidator implements IPropertyValidator {


    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {

        if (value == null || value.toString().length() == 0) return;

        if (! GenericValidator.isCreditCard(value.toString())) {
            errors.add("creditcard_validation_error", propertyName);
        }

    }

}

Q.50 How to add EMAIL_LIST stereotype and validation class?

Will be part of OpenXava 2.2.4 and 3.0beta5!

sf.net post

1) Add to Application/xava/default-size.xml

<for-stereotype name="EMAIL_LIST" size="200"/>

2) Add to Application/xava/editors.xml

<editor url="textEditor.jsp"> 
  <for-stereotype stereotype="EMAIL_LIST"/> 
</editor> 

3) Add to Application/xava/stereotype-type-default.xml

<for stereotype="EMAIL_LIST" type="String"/> 

4) Add to Application/i18n/messages_en.properties file

email_list_validation_error=Email list should contain valid emails separated by commas

5) Validation class

package org.openxava.validators;

import org.openxava.validators.IPropertyValidator;
import org.openxava.util.Messages;

import org.apache.commons.validator.GenericValidator;
import java.util.StringTokenizer;

/**
 * @author Janesh Kodikara
 */

public class EmailListValidator implements IPropertyValidator {

    public void validate(Messages errors, Object value, String propertyName, String modelName) throws Exception {


        if (value == null || value.toString().length() == 0) 
            return;

        StringTokenizer emailAddresses = new StringTokenizer(value.toString(), ",");
        while(emailAddresses.hasMoreTokens()) {
            if (! GenericValidator.isEmail(emailAddresses.nextToken())) {
                errors.add("email_list_validation_error", propertyName);
            }
        }
    }

}

Q.51 How to save model details when a action is called?

Is it possible to save model details when a action is called?

Yes.

An option is to make your action a child of SaveAction, and put the property resetAfter to false. Then you only need to:

public void execute() throws Exception {
  super.execute();
  // Here the code of you action.
}

Or you can save the data from the view directly using MapFacade.

Look at the code of SaveAction in order to learn how to use MapFacade.


Q.52 Converter for TIME stereotype.

Will be part of OpenXava 2.2.5.


  • By default, when you use "TIME" stereotype you want to work with String for manage time data, both at Java level and DB level. This case is:
  <property name="mytime" stereotype="TIME"/>


  • If you want to work with java.sql.Time usually you use the Java type directly, and you don't use TIME stereotype. In this way you use java.sql.Time for Java and DB. Code is:
  <property name=mytime" type="java.sql.Time"/>


  • New converter is only for the case that you want to work with String at Java level and with java.sql.Time at DB level:
  <property name="mytime" stereotype="TIME"/>
  ...
  <property-mapping ... >
    <converter class="org.openxava.converters.StringTimeConverter"/>
  </property-mapping>
  ...
  • New converter:
package org.openxava.converters;

import org.openxava.converters.IConverter;
import org.openxava.converters.ConversionException;

import java.sql.Time;

/**
 * In java a <tt>String</tt> and in database a
 * <tt>java.sql.Time</tt>. <p>
 *
 * @author Janesh Kodikara
 */
public class StringTimeConverter implements IConverter {

    private Time time;
    private String timeStamp;

    public Object toDB(Object o) throws ConversionException {


        if (o == null) return null;
        if (!(o instanceof String)) {
            throw new ConversionException("conversion_db_string_expected");
        }

        timeStamp = o.toString() + ":00";
        time = Time.valueOf(timeStamp);
        return time;
    }

    public Object toJava(Object o) throws ConversionException {
        if (o == null) return null;
        if (!(o instanceof java.sql.Time)) {
            throw new ConversionException("conversion_java_sqltime_expected");
        }

        time = (Time) o;
        timeStamp = time.toString();
        timeStamp = timeStamp.substring(0, 5);

        return timeStamp;
    }
}


Q.53 How to add a string to description list?

Section 4.3.6 of Reference guide.

Is it possible to add text to a description list?

Need to display book title along with Author and separate the variable with a string (e.g ":" or "by")

<reference-view reference="book">
  <descriptions-list description-properties="title, author.firstName, author.secondName"/>
</reference-view>

or

<reference-view reference="subfamily" create="false">

	<!-- In this case description-property can be omited -->
	<descriptions-list  
		description-property="description" 
		depends="family"
		condition="${family.number} = ?"/>

</reference-view>


First suggestion: Use Calculated property

<component name="Book">
  <entity>
    ...
    <property name="authorFullName" type="String" size="25">
	<calculator class="org.openxava.calculators.ConcatCalculator">
		<set property="string1" from="author.firstName"/>				
		<set property="separator" value=" "/>
		<set property="string2" from="author.secondName"/>
	</calculator>
    </property>

    <property name="fullTitle" type="String" size="60">
	<calculator class="org.openxava.calculators.ConcatCalculator">
		<set property="string1" from="title"/>				
		<set property="separator" value=" by "/>
		<set property="string2" from="authorFullName"/>
	</calculator>
    </property>

  </entity>
...
</component>

<component ... >
  <view>
    <reference-view reference="book"> 
      <descriptions-list description-properties="fullTitle"/> 
    </reference-view> 
  </view>
</component>


Second suggestion: Develop new functionality

> Way to distinguish properties from strings.

Your way is good, and I think that natural for an OX developer. Use other attribute, 'description-message-id'.


> Guide how to develop new functionality?

The code for concatenating the description of description list is in the class.

org.openxava.calculators.DescriptionsCalculator

After this works, we can consider using 'description-message-id', this will involve to modify the dtd and parser.

Third suggestion: New functionality with i18n

<descriptions-list description-properties="title, author.firstName, author.secondName" pattern-id="descriptions_list_book_title"
/>


And in YourApplication-messages_en.properties you have:

descriptions_list_book_title={0} by {1} {2} 

Q.54 How to remove image?

Edit imageEditor.jsp, and add a new action to reset the image, inside the below block.

<% if (editable) { %>

Add the action in ImageEditor controller of default-controllers.xml.

Have a look to the code of LoadImageAction.


Q.55 How to modify OpenXava DTD?

Add new XML element or xml attribute to below class. Java class which holds all dtd element and attribute names is:

OpenXava/src/org/openxava/util/xmlparse/XmlElementsNames.java


Q.56 How to add new property to xava.properties file?

Edit file:

OpenXava/src/org/openxava/util/XavaPreferences.java

Added new method which returns value of new property:

public boolean isMapFacadeAutoCommit() 
{
    return "true".equalsIgnoreCase(getProperties().getProperty("mapFacadeAutoCommit", "false").trim());
}


Q.57 How to sort reference list with condition?

Error is thrown if column in condition is not included in the FROM part.

Because OX uses description-properties to determine which tables must be included in FROM part.

A simple workaround is to add your property to description properties, in this way:

<reference-view reference="releasebyQA" frame="false">
  <descriptions-list description-property="name, designation.department.id" 
                     condition="${designation.department.id} ='QA'"/>
</reference-view>


Q.58 How to create Date Validation class?

package org.openxava.validators;

import org.apache.commons.validator.routines.DateValidator;
import org.openxava.util.Messages;
import java.util.Date;


/**
 * @author Janesh Kodikara
 */

public class XavaDateValidator {

    DateValidator validator;

    public XavaDateValidator() {
        validator = new DateValidator();
    }

    public boolean isSameDate(Date startDate, Date endDate) {
        boolean isSame = false;

        if (validator.compareDates(startDate, endDate, null) == 0) {
            isSame = true;
        }
        return isSame;
    }

    public void isSameDate(Messages errors, String startFieldName, Date startDate, String endFieldName, Date endDate) {

        if (startDate == null || endDate == null) return;

        if (validator.compareDates(startDate, endDate, null) == 0) {
            errors.add("same_dates_error", endFieldName, startFieldName);
        }
    }

    public boolean isFutureDate(Date value) {
        boolean isSame = false;
        Date today = new Date();
        if (validator.compareDates(value, today, null) > 0) {
            isSame = true;
        }
        return isSame;
    }

    public void isFutureDate(Messages errors, String fieldName, Date value) {
        Date today = new Date();

        if (value == null) return;

        if (validator.compareDates(value, today, null) > 0) {
            errors.add("date_in_future_error", fieldName);
        }
    }

    public boolean isPastDate(Date value) {
        boolean isSame = false;
        Date today = new Date();
        if (validator.compareDates(value, today, null) < 0) {
            isSame = true;
        }
        return isSame;
    }
    public void isPastDate(Messages errors, String fieldName, Date value) {
        Date today = new Date();

        if (value == null) return;

        if (validator.compareDates(value, today, null) > 0) {
            errors.add("past_date_error", fieldName);
        }
    }


    public void isStartDateAfter(Messages errors, String startFieldName, Date startDate, String endFieldName, Date endDate) {
        if (startDate == null || endDate == null) return;

        if (validator.compareDates(startDate, endDate, null) > 0) {
            errors.add("start_date_greater_error", endFieldName, startFieldName);
        }
    }
}


Q.58 How to use OX and bind to a web service layer rather than to a database?

OpenXava has a pluggable persistence provider. Package org.openxava.model.impl has implementation of EJB CMP2, Hibernate and JPA persistece provider. Own persistence provider can be created to save and store the data by custom way.

The only problem is that Tabbular data retrieval is done via JDBC. That's why additionally own TabProvider have to be written. For example look at:

org.openxava.model.tab.impl


Q.59 How to customize Looks and Feel of OpenXava?

Option to create your own way for groups and sections, for entire application or for a concrete entity/view. This will be included in some future version (OX3.1.x).

Developer can create his own style (in org.openxava.web.style) and overwrite bellow methods:

getSection(), getSectionTableAttributes(),
getSectionBarStartDecoration(), getActiveSectionTabStartDecoration(),
getActiveSectionTabEndDecoration(), getSectionTabStartDecoration(),
getSectionTabEndDecoration(), getSectionBarEndDecoration(), 
getSectionActive()

Though this is limited, applied to all entities of all applications, and you will lose the portal look & feel adaptation of OX.


Q.60 How to get autogenerated ID after save?

public void execute() throws Exception {
  setResetAfter(false); // In this way the key will be refreshed from saved Entity to View
  super.execute(); // Save, and after it you have the oid in View
  id = (String)getView().getKeyValues().get("id");  // Now, id has value
}

Q.60 How to show a web site? How to make editor for a web url?

Editor for a web url that allows a link to be clicked to show the text's site:

In <myproject>/xava/editors.xml I have added this at the beginning of the file to associate the stereotype “WEBURL” with this specific editor:

<editors>
   ...
   <editor name="WebURL" url="webUrlEditor.jsp">
       <for-stereotype stereotype="WEBURL"/>
   </editor>
   ...
</editors>


Q.61 How to filter record by current user?

<controller name="PreUser">
  <action name="filterByCurrentUserid" hidden="true" on-init="true" on-each-request="true" class="com.myschool.actions.filterByCurrentUserid">
    <use-object name="xava_mainTab" />
    <use-object name="xava_tab" />
  </action>
</controller>