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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
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 ).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** 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
1 2 3 4 5 6 7 8 9 10 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
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
1 2 3 4 5 6 7 8 9 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
public static String <strong>getRegexForDateFormat</strong>( 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 <strong>getRegexSymbol</strong>(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 <strong>isValidDateFormat</strong>( 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 <strong>getRegexFormat</strong> (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 <strong>isValidDate</strong>(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.
1 2 |
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