Min & Max values configuration in ADempiere

Problem Statement:

In any Business Application, we will have common need to validate the input data. One of the common and most used validation is range validation i.e. input data must be with in specified range.  In ADempiere we fulfill this requirement by writing one callout funtion for each field.

If there is a way we can do these validation with simple configuration changes then we can save lot of development effort and functional consultant can change range dynamically as per the need. ADempiere does not have this feature inbuilt, but with minor code changes we can achieve this.

Scope of Article:

As a part of this Article, we will be cover following topics

  • How range validation works
  • Changes we need to make in ADempiere to support range validation for Quantity,Number,Amount,Integer,Date Reference types in ADempiere.

Prerequisites:

  • Need to have basic knowledge on ADempiere window creation with validations.
  • Java SE knowledge is required.

Technology :

  • Adempiere 342 or above

Current Behavior :

  • By default ADempiere doesn’t support Min & Max values validations.We need to make code changes to have validations in place.
  • Without making any code changes, following are the test cases of Min & Max values functionality in default ADempiere.

Min & Max values configuration for Date
By login with System/SystemAdministrator, configure Min & Max Date(Date Format : yyyy/MM/dd) values for Account Date of  M_InOut in Table and Column.Now test this Min & Max configuration of Account Date in Shipment ( Customer ) window.

Max Value Testing :
Entered 26/09/2016 in Account Date field, But there is no Max Value validation error.Min Value Testing :

Entered 26/09/2008 in Account Date field, But there is no Min Value validation error.Min & Max values configuration for Integer ( Reference Type )

Configure Min & Max values for No Packages Integer field of M_InOut in Table and Column.Now test this Min & Max configuration of No Packages field in Shipment ( Customer ) window.

Max Value Testing :
Entered 35 in No Packages field, But there is no Max Value validation error.Min Value Testing :

Entered 2 in No Packages field, But there is no Min Value validation error.To support Min & Max values configuration, we need to make code changes.

Changes Details :

  • Added following new Callout file ( CalloutTable.java) to check the validations and raise validaton errors.
package org.compiere.model;
import java.util.Properties;
import org.adempiere.exceptions.AdempiereException;
import org.compiere.util.DisplayType;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.wtc.util.EagleConstants;
import org.wtc.util.WTCUtil;
public class CalloutTable extends CalloutEngine {
/**
* Validating Min Max Numeric/Dates
* @return Return Empty String Value
*/public String validateMinMaxValues(Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value) {
if (isCalloutActive() || null == value ) {
return "";
}
String minOrMaxValue = ( String ) value;
if ( ! Util.isEmpty( minOrMaxValue, Boolean.TRUE ) ) {
validateValue(minOrMaxValue, ctx, mTab, mField);
}
return "";
}
private void validateValue( String validationcode, Properties ctx, GridTab mTab, GridField mField ) {
int referenceId = ( Integer ) mTab.getValue( X_AD_Column.COLUMNNAME_AD_Reference_ID );
if ( referenceId > 0 ) {
if ( DisplayType.isNumeric( referenceId ) ) {
if ( ! compareWithSpecialCharacters( validationcode, referenceId ) ) {
mTab.setValue( mField.getColumnName(), null );
if ( DisplayType.Integer == referenceId ) {
throw new AdempiereException( Msg.getMsg(ctx, "NumberMinMaxValidation") );
}
else {
throw new AdempiereException( Msg.getMsg(ctx, "DecimalValidation") );
}
}
} else if ( DisplayType.isDate( referenceId ) ) {
if ( ! checkDateFormat(validationcode ) ) {
mTab.setValue( mField.getColumnName(), null );
throw new AdempiereException( Msg.getMsg(ctx, "DateMinMaxValidation") );
}
}
}
}
/**
* @return True if Value have only numeric
*/private boolean compareWithSpecialCharacters(String str, int referenceId )  {
boolean haveSpecialChars = Boolean.TRUE;
if ( DisplayType.Integer == referenceId && str.matches( EagleConstants.EAGLERP_INTEGER_REGEX ) ) {
haveSpecialChars = Boolean.FALSE;
} else if ( ( referenceId == DisplayType.Amount
|| referenceId == DisplayType.Number
|| referenceId == DisplayType.CostPrice
|| referenceId == DisplayType.Quantity )
&& str.matches( EagleConstants.EAGLERP_BIGDECIMAL_REGEX ) ) {
haveSpecialChars = Boolean.FALSE;
}
return ! haveSpecialChars ;
}
/**
* @param dateString
* @return true if Date Format is correct
*/private boolean checkDateFormat(String dateString) {
boolean isCorrectDate = Boolean.FALSE;
//
// Get the Regex based on configured date format
//
String regex = WTCUtil.getRegexForDateFormat( EagleConstants.DATE_FORMATE );
//
// modify the regex format For Numbers
//
regex = regex.replace("S", "d");
if ( ! Util.isEmpty(regex, Boolean.TRUE )
&& dateString.matches( regex )
&& WTCUtil.isValidDate( dateString , EagleConstants.DATE_FORMATE ) ) {
isCorrectDate = Boolean.TRUE;
}
return isCorrectDate;
 }
}
  • Made following changes in EagleConstants.java ( In this file, we have declared the all required constants ).
/** Integer Format Regex*/public static final String EAGLERP_INTEGER_REGEX = "[0-9]*";

/** BigDecimal Format Regex*/public static final String EAGLERP_BIGDECIMAL_REGEX = "[0-9]*\\.[0-9]*";

public static final String DATE_FORMATE = "yyyy/MM/dd";

// Below 4 staltements are Meassages
public static final String DATE_GREATERTHAN   = "DATE_GREATERTHAN";
public static final String DATE_LESSTHAN      = "DATE_LESSTHAN";
public static final String VALUE_GREATERTHAN  = "VALUE_GREATERTHAN";
public static final String VALUE_LESSTHAN     = "VALUE_LESSTHAN";
  • Made following changes in ADTabPanel.java
add following Import  statements.
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.compiere.util.DisplayType;
import org.compiere.util.Language;
import org.compiere.util.Msg;
import org.wtc.util.EagleConstants;
  • Added following new Methods in ADTabPanel.java
private void minMaxValidation(GridField mField) {
Integer displayType = mField.getDisplayType();
String minString = mField.getValueMin();
String maxString = mField.getValueMax();
Object currValue = (Object) mField.getValue();
if (null == minString && null == maxString) {
return;
}
GridTab mTab = mField.getGridTab();
// Display Type Comparing With Integer
if (currValue instanceof Integer
&& displayType == DisplayType.Integer) {
validateMinMaxInteger(mTab, mField, minString, maxString);
}
// Display Type Compare with Amount And Quantity and Numbers
else if (currValue instanceof BigDecimal
&& ( displayType == DisplayType.Amount
|| displayType == DisplayType.Quantity
|| displayType == DisplayType.Number
|| displayType == DisplayType.Integer ) ) {
validateMinMaxNumbers(mTab, mField, minString, maxString);
}
// Display Type Comparing With Date
else if (currValue instanceof Timestamp
&& displayType == DisplayType.Date) {
validateMinMaxDate(mTab, mField, minString, maxString);
}
// Added new validateMinMaxNumbers() method
/**
* Min And Max Validations for Numbers
* @param mField
* @param minString
* @param maxString
*/private void validateMinMaxNumbers(GridTab mTab, GridField mField, String minString,
String maxString) {
BigDecimal currValue = Env.ZERO;
String msg = "";
try {
currValue = ( BigDecimal ) mField.getValue(); // Getting current Value
if (currValue != null) {
if ( ! Util.isEmpty( minString, Boolean.TRUE ) ) {
BigDecimal minValue = new BigDecimal(minString);
if ( minValue != null && minValue.compareTo(currValue) > 0 ) {
// setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg(Env.getCtx(),
EagleConstants.VALUE_GREATERTHAN,
new Object[] { minValue } );
FDialog.error( windowNo, this, msg );
mTab.setValue( mField.getColumnName(), minValue ); // 201202091233
return ;
}
}
if ( ! Util.isEmpty( maxString, Boolean.TRUE ) ) {
BigDecimal maxValue = new BigDecimal( maxString );
if ( maxValue != null && maxValue.compareTo( currValue ) < 0 ) {
// setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg( Env.getCtx(),
EagleConstants.VALUE_LESSTHAN,
new Object[] { maxValue } );
FDialog.error( windowNo, this, msg );
mTab.setValue( mField.getColumnName(), maxValue ); // 201202091233
}
}
}
} catch (NumberFormatException e) {
logger.log( Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + " " + e.getMessage() );
} catch (Exception e) {
logger.log( Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + " " + e.getMessage() );
}
}
// Introduced validateMinMaxInteger() new method.
/**
* @param mField
* @param minString
* @param maxString
*/private void validateMinMaxInteger(GridTab mTab, GridField mField, String minString,String maxString) {
Integer currValue = null;
String msg = "";
try {
currValue = (Integer) mField.getValue(); // Getting current Value
if ( currValue != null ) {
if ( ! Util.isEmpty( minString, Boolean.TRUE ) ) {
Integer minValue = Integer.valueOf(minString);
if ( minValue != null && minValue.compareTo( currValue) > 0 ) {
// And setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg( Env.getCtx(),
EagleConstants.VALUE_GREATERTHAN,
new Object[] { minValue } );
FDialog.error( windowNo, this, msg );
mTab.setValue( mField.getColumnName(), minValue );
return ;
}
}
if ( ! Util.isEmpty( maxString, Boolean.TRUE ) ) {
Integer maxValue = Integer.valueOf( maxString );
if ( maxValue != null && maxValue.compareTo( currValue ) < 0 ) {
// And setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg ( Env.getCtx(),
EagleConstants.VALUE_LESSTHAN,
new Object[] { maxValue } );
FDialog.error ( windowNo, this, msg );
mTab.setValue( mField.getColumnName(), maxValue );
}
}
}
} catch ( NumberFormatException e ) {
logger.log( Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + " " + e.getMessage() );
} catch (ClassCastException e) {
logger.log( Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + " " + e.getMessage() );
} catch ( Exception e ) {
logger.log( Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + " " + e.getMessage() );
}
}
// Added validateMinMaxDate() new method
/**
* This Method validating  the min max values
* @param mField
* @param minString
* @param maxString
*/private void validateMinMaxDate(GridTab mTab, GridField mField, String minString, String maxString){
String msg = "";
Timestamp currValue = (Timestamp) mField.getValue();
// Getting Min and Max values as String And parsing to date using SimpleDate Format
if (currValue != null) {
// As per user Login language, we getting date format
Timestamp minDate = null;
Timestamp maxDate = null;
try {
if ( ! Util.isEmpty( minString, Boolean.TRUE ) ) {
minDate = getParseDateFormat( minString );
//new Timestamp(format.parse(minString).getTime());
}
if ( ! Util.isEmpty( maxString, Boolean.TRUE ) ) {
maxDate = getParseDateFormat( maxString );
//new Timestamp(format.parse(maxString).getTime());
}
if (minDate != null) {
// Configured minimum Date Comparing with Current Value
if (minDate.after(currValue)) {
// And setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg(
Env.getCtx(),
EagleConstants.DATE_GREATERTHAN,
new Object[] { getParseDate(minDate)
});
FDialog.error(windowNo, this, msg);
mTab.setValue( mField.getColumnName(), minDate );
return ;
}
}
if (maxDate != null) {
// Configured maximum date Comparing with Current Value
if (maxDate.before(currValue)) {
// And setting Current Value to field (Override user Entered Value) And populating one Error Message
msg = Msg.getMsg(
Env.getCtx(),
EagleConstants.DATE_LESSTHAN,
new Object[] { getParseDate(maxDate)
});
FDialog.error(windowNo, this, msg);
mTab.setValue( mField.getColumnName(), maxDate );
}
}
}
catch (ClassCastException e) {
logger.log(Level.SEVERE, "Parsing Failed at Min or Max values "
+ currValue + "---" + e.getMessage());
}
catch (Exception e) {
logger.log(
Level.SEVERE,"Parsing Failed at Min Date " + minString + " or Max Date" + maxString + " ---- " + e.getMessage());
}
}
}
/**
* parsing Date format
* @param dateString
* @return Timestmap
*/private Timestamp getParseDateFormat(String dateString ) {
SimpleDateFormat format = null;
Timestamp parseTimestamp = null;
//
// String dateFormat = DisplayType.getDateFormat(DisplayType.Date,AEnv.getLanguage ( Env.getCtx() ) ).toPattern();
//
format = new SimpleDateFormat( EagleConstants.DATE_FORMATE );
try {
parseTimestamp = new Timestamp(format.parse(dateString).getTime());
} catch (ParseException e) {
logger.log(Level.SEVERE, "Parsing Failed at Min or Max values "
+dateString + "--- "+ e.getMessage());
} catch (Exception e) {
logger.log(Level.SEVERE, " Parsing Failed at Min or Max values "
+dateString + "--- "+ e.getMessage());
}
return parseTimestamp ;
}
/**
* Parsing the date, based on the Language
* @param timestamp
* @return stringfomateDate
*/private String getParseDate(Timestamp timestamp) {
Language language = Language.getLoginLanguage();
SimpleDateFormat format = language.getDateFormat();
String parseTimestamp = format.format(new Date(timestamp.getTime()) );
return parseTimestamp;
}
}
  • Made following changes in dataStatusChanged(DataStatusEvent e) method of ADTabPanel.java
 // After this GridField mField = gridTab.getField(col);

 // We are checking this before execution of callout Min And Max Value Validation

 if( mField != null && ( mField.getValueMin() != null || mField.getValueMax() != null ) )  {

minMaxValidation( mField );

 }
  • Added below methods in org.wtc.util.WTCUtil.Java
public static String getRegexForDateFormat( String dateFormat ) {
if( null == dateFormat ){
return null;
}
String regexdateformat = "";
String symbol = getRegexSymbol( dateFormat );
if( ! dateFormat.isEmpty() ) {
regexdateformat = getRegexFormat( dateFormat, symbol );
}
return regexdateformat;
}

private static String getRegexSymbol(String format) {
String symbol = "";
if ( isValidDateFormat( format ) ) {
String[] symbols = { "/", " ", ".", "-" };
for (int i = 0; i < symbols.length; i++) {
if ( format.contains( symbols[i] ) ) {
symbol = symbols[i];
break;
}
}
}
return symbol;
}

private static boolean isValidDateFormat( String format ) {
String[] symbols = {"/", " ", ".", "-" };
int count = 0;
boolean isValid = Boolean.FALSE;
for ( int i = 0; i < symbols.length; i++ ) {
if ( format.contains( symbols[i] ) ) {
isValid = Boolean.TRUE;
count++;
}
}
if( ! isValid || count != 1 ) {
return Boolean.FALSE;
}
return isValid;
}
private static String getRegexFormat (String format, String symbol ) {
StringBuffer buffer = new StringBuffer();
if ( Util.isEmpty(format, Boolean.TRUE ) ||
Util.isEmpty(symbol, Boolean.TRUE ) ) {
return buffer.toString();
}
StringTokenizer token = new StringTokenizer(format, symbol);
int noOfTokens = token.countTokens();
while ( token.hasMoreTokens() ) {
int nextTokenLength = token.nextToken().length();
buffer.append("(");
while( nextTokenLength > 0 ) {
buffer.append("\\S" );
nextTokenLength--;
}
buffer.append(")");
if( noOfTokens > 1 ) {
buffer.append( symbol );
noOfTokens--;
}
}
return buffer.toString();
}
public static boolean isValidDate(String stringDate, String dateFormat ) {
boolean isValidDate = Boolean.FALSE;
// StringDate or dateFormat is anyone null or "" then return as FALSE
//
if( Util.isEmpty(stringDate, Boolean.TRUE ) || Util.isEmpty( dateFormat, Boolean.TRUE ) ) {
return isValidDate;
}
//
// Creating a simpleDateFormat object With using out dateFormat
SimpleDateFormat simpleDateFormat = new SimpleDateFormat( dateFormat );
try {
Date date = simpleDateFormat.parse( stringDate );
if( null != date ) {
isValidDate = Boolean.TRUE;
}
} catch (Exception e) {
return isValidDate;
}
return isValidDate;
}
  • Associate newly created Callout to MinValue and MaxValue columns of AD_Column Table by executing following update statements in database.
UPDATE AD_Column SET Callout='org.compiere.model.CalloutTable.validateMinMaxValues',Updated=TO_TIMESTAMP('2012-02-04 19:00:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=3388;
UPDATE AD_Column SET Callout='org.compiere.model.CalloutTable.validateMinMaxValues',Updated=TO_TIMESTAMP('2012-02-04 19:00:02','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=3389;
  • And create following required messages in AD_Message table to show validation error messages.
  • After making  above changes, Test the same scenarios to check Min & Max validations.

Max Account Date validation testing
Entered 10/10/2014 in Account Date field and see the error message.Min Account Date validation testing.

Entered 26/09/2010 in Account Date field and see the error message.Max No Packages validation testing.

Entered 12 in No Packages field and see the error message.Min No Packages validation testing.

Entered 2 in No Packages field and see the error message.We are also supporting Quantity,Number,Amount Reference Types to check their min and max values and raise validation messages.

Limitation : Whenever model(MInOut.java) new instance is created in the code and saved, in this case validations doesn’t work, they will work only when data is entered through window.

Summary:

As a part of this article we come to know what are the necessary changes, we need to do to have min and max values validations for specific fields in ADempiere. Hope you have enjoyed reading this article.

Walking Tree promotes ADempiere and we support the users as well as the developers to ensure that the business is able to take complete advantage of ADempiere’s wonderful capability. In case you are looking for a professional assistance then do visit our website to get in touch with us.

References:

  • http://www.adempiere.com/Callout
  • http://www.adempiere.com/Development
Ravindar Thummarajula: Technical Lead at WalkingTree, A Professional Full Stack Engineer, Expertise in Java/J2EE, Angular, Pentaho, Microservices, Grails, MongoDB, Elasticsearch, ADempiere, Apache OFBiz, JMeter technologies. Has the good domain knowledge of AutoCare Industry, Construction, Power sector.
Related Post

This website uses cookies.