View Javadoc
1   /*
2    * Copyright 2006 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.document.validation.impl;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.kuali.ole.coa.businessobject.A21IndirectCostRecoveryAccount;
26  import org.kuali.ole.coa.businessobject.A21SubAccount;
27  import org.kuali.ole.coa.businessobject.IndirectCostRecoveryAccount;
28  import org.kuali.ole.coa.businessobject.IndirectCostRecoveryRateDetail;
29  import org.kuali.ole.coa.businessobject.SubAccount;
30  import org.kuali.ole.coa.service.SubFundGroupService;
31  import org.kuali.ole.sys.OLEConstants;
32  import org.kuali.ole.sys.OLEKeyConstants;
33  import org.kuali.ole.sys.OLEPropertyConstants;
34  import org.kuali.ole.sys.context.SpringContext;
35  import org.kuali.ole.sys.service.UniversityDateService;
36  import org.kuali.rice.kns.document.MaintenanceDocument;
37  import org.kuali.rice.kns.service.DataDictionaryService;
38  import org.kuali.rice.krad.util.ObjectUtils;
39  
40  /**
41   * This class implements the business rules specific to the {@link SubAccount} Maintenance Document.
42   */
43  public class SubAccountRule extends IndirectCostRecoveryAccountsRule {
44  
45      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SubAccountRule.class);
46  
47      protected SubAccount oldSubAccount;
48      protected SubAccount newSubAccount;
49  
50      /**
51       * This performs rules checks on document approve
52       * <ul>
53       * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
54       * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
55       * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
56       * </ul>
57       * This rule fails on business rule failures
58       * 
59       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
60       */
61      protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
62          LOG.debug("Entering processCustomApproveDocumentBusinessRules()");
63  
64          // check that all sub-objects whose keys are specified have matching objects in the db
65          boolean success = checkForPartiallyEnteredReportingFields();
66  
67          // process CG rules if appropriate
68          success &= checkCgRules(document);
69  
70          return success;
71      }
72  
73      /**
74       * This performs rules checks on document route
75       * <ul>
76       * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
77       * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
78       * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
79       * </ul>
80       * This rule fails on business rule failures
81       * 
82       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
83       */
84      protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
85          LOG.debug("Entering processCustomRouteDocumentBusinessRules()");
86  
87          boolean success = true;
88  
89          // check that all sub-objects whose keys are specified have matching objects in the db
90          success &= checkForPartiallyEnteredReportingFields();
91  
92          // process CG rules if appropriate
93          success &= checkCgRules(document);
94  
95          success &= super.processCustomRouteDocumentBusinessRules(document);
96          return success;
97      }
98  
99      /**
100      * This performs rules checks on document save
101      * <ul>
102      * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
103      * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
104      * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
105      * </ul>
106      * This rule does not fail on business rule failures
107      * 
108      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
109      */
110     protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
111 
112         boolean success = true;
113 
114         LOG.debug("Entering processCustomSaveDocumentBusinessRules()");
115 
116         // check that all sub-objects whose keys are specified have matching objects in the db
117         success &= checkForPartiallyEnteredReportingFields();
118 
119         // process CG rules if appropriate
120         success &= checkCgRules(document);
121 
122         return success;
123     }
124 
125     /**
126      * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
127      * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
128      * all sub-objects from the DB by their primary keys, if available.
129      * 
130      * @param document - the maintenanceDocument being evaluated
131      */
132     public void setupConvenienceObjects() {
133 
134         // setup oldAccount convenience objects, make sure all possible sub-objects are populated
135         oldSubAccount = (SubAccount) super.getOldBo();
136         refreshSubObjects(oldSubAccount);
137 
138         // setup newAccount convenience objects, make sure all possible sub-objects are populated
139         newSubAccount = (SubAccount) super.getNewBo();
140         refreshSubObjects(newSubAccount);
141         
142         //icr rule checking setup
143         if (newSubAccount.getA21SubAccount() != null){
144             List<IndirectCostRecoveryAccount> icrAccountList = new ArrayList<IndirectCostRecoveryAccount>(
145                     newSubAccount.getA21SubAccount().getA21ActiveIndirectCostRecoveryAccounts());
146             setActiveIndirectCostRecoveryAccountList(icrAccountList);
147             setBoFieldPath(OLEPropertyConstants.A21INDIRECT_COST_RECOVERY_ACCOUNTS);
148         }
149     }
150     
151     /**
152      * Refreshes the references of account
153      * 
154      * @param subaccount SubAccount
155      */
156     void refreshSubObjects(SubAccount subaccount) {
157         if (subaccount != null) {
158             if (subaccount.getA21SubAccount() != null) {
159                 subaccount.getA21SubAccount().refreshNonUpdateableReferences();
160                 // refresh contacts
161 //                if (subaccount.getA21SubAccount().getA21IndirectCostRecoveryAccounts() != null) {
162 //                    for (A21IndirectCostRecoveryAccount icra : subaccount.getA21SubAccount().getA21IndirectCostRecoveryAccounts()) {
163 //                        icra.refreshNonUpdateableReferences();
164 //                    }
165 //                }
166             }
167         }
168     }
169 
170     /**
171      * This checks that the reporting fields are entered altogether or none at all
172      * 
173      * @return false if only one reporting field filled out and not all of them, true otherwise
174      */
175     protected boolean checkForPartiallyEnteredReportingFields() {
176 
177         LOG.debug("Entering checkExistenceAndActive()");
178 
179         boolean success = true;
180         boolean allReportingFieldsEntered = false;
181         boolean anyReportingFieldsEntered = false;
182 
183         // set a flag if all three reporting fields are filled (this is separated just for readability)
184         if (StringUtils.isNotEmpty(newSubAccount.getFinancialReportChartCode()) && StringUtils.isNotEmpty(newSubAccount.getFinReportOrganizationCode()) && StringUtils.isNotEmpty(newSubAccount.getFinancialReportingCode())) {
185             allReportingFieldsEntered = true;
186         }
187 
188         // set a flag if any of the three reporting fields are filled (this is separated just for readability)
189         if (StringUtils.isNotEmpty(newSubAccount.getFinancialReportChartCode()) || StringUtils.isNotEmpty(newSubAccount.getFinReportOrganizationCode()) || StringUtils.isNotEmpty(newSubAccount.getFinancialReportingCode())) {
190             anyReportingFieldsEntered = true;
191         }
192 
193         // if any of the three reporting code fields are filled out, all three must be, or none
194         // if any of the three are entered
195         if (anyReportingFieldsEntered && !allReportingFieldsEntered) {
196             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_RPTCODE_ALL_FIELDS_IF_ANY_FIELDS);
197             success &= false;
198         }
199 
200         return success;
201     }
202 
203     /**
204      * This checks to make sure that if cgAuthorized is false it succeeds immediately, otherwise it checks that all the information
205      * for CG is correctly entered and identified including:
206      * <ul>
207      * <li>If the {@link SubFundGroup} isn't for Contracts and Grants then check to make sure that the cost share and ICR fields are
208      * not empty</li>
209      * <li>If it isn't a child of CG, then the SubAccount must be of type ICR</li>
210      * </ul>
211      * 
212      * @param document
213      * @return true if the user is not authorized to change CG fields, otherwise it checks the above conditions
214      */
215     protected boolean checkCgRules(MaintenanceDocument document) {
216 
217         boolean success = true;
218 
219         // short circuit if the parent account is NOT part of a CG fund group
220         boolean a21SubAccountRefreshed = false;
221         if (ObjectUtils.isNotNull(newSubAccount.getAccount())) {
222             if (ObjectUtils.isNotNull(newSubAccount.getAccount().getSubFundGroup())) {
223 
224                 // compare them, exit if the account isn't for contracts and grants
225                 if (!SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newSubAccount.getAccount().getSubFundGroup())) {
226 
227                     // KULCOA-1116 - Check if CG CS and CG ICR are empty, if not throw an error
228                     if (checkCgCostSharingIsEmpty() == false) {
229                         putFieldError("a21SubAccount.costShareChartOfAccountCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_CS_INVALID, new String[] { SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingAttributeLabel(), SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingValueForMessage() });
230                         success = false;
231                     }
232 
233                     if (checkCgIcrIsEmpty() == false) {
234                         putFieldError("a21SubAccount.indirectCostRecoveryTypeCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_ICR_INVALID, new String[] { SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingAttributeLabel(), SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingValueForMessage() });
235                         success = false;
236                     }
237 
238                     // KULRNE-4660 - this isn't the child of a CG account; sub account must be ICR type
239                     if (!ObjectUtils.isNull(newSubAccount.getA21SubAccount())) {
240                         // KFSMI-798 - refresh() changed to refreshNonUpdateableReferences()
241                         // All references for A21SubAccount are non-updatable
242                         newSubAccount.getA21SubAccount().refreshNonUpdateableReferences();
243                         a21SubAccountRefreshed = true;
244                         if (StringUtils.isEmpty(newSubAccount.getA21SubAccount().getSubAccountTypeCode()) || !newSubAccount.getA21SubAccount().getSubAccountTypeCode().equals(OLEConstants.SubAccountType.EXPENSE)) {
245                             putFieldError("a21SubAccount.subAccountTypeCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_SUB_ACCT_TYPE_CODE_INVALID, new String[] { SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingAttributeLabel(), SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingValueForMessage() });
246                             success = false;
247                         }
248                     }
249 
250                     return success;
251                 }
252             }
253         }
254 
255         A21SubAccount a21 = newSubAccount.getA21SubAccount();
256         
257         // short circuit if there is no A21SubAccount object at all (ie, null)
258         if (ObjectUtils.isNull(a21)) {
259             return success;
260         }
261 
262         // FROM HERE ON IN WE CAN ASSUME THERE IS A VALID A21 SUBACCOUNT OBJECT
263 
264         // KFSMI-6848 since there is a ICR Collection Account object, change refresh to perform 
265         // manually refresh the a21SubAccount object, as it wont have been
266         // refreshed by the parent, as its updateable
267         // though only refresh if we didn't refresh in the checks above
268         
269         if (!a21SubAccountRefreshed) {
270             //preserve the ICRAccounts before refresh to prevent the list from dropping
271             List<A21IndirectCostRecoveryAccount>icrAccounts =a21.getA21IndirectCostRecoveryAccounts(); 
272             a21.refresh();
273             a21.setA21IndirectCostRecoveryAccounts(icrAccounts);
274             
275         }
276 
277         // C&G A21 Type field must be in the allowed values
278         if (!OLEConstants.SubAccountType.ELIGIBLE_SUB_ACCOUNT_TYPE_CODES.contains(a21.getSubAccountTypeCode())) {
279             putFieldError("a21SubAccount.subAccountTypeCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_INVALI_SUBACCOUNT_TYPE_CODES, OLEConstants.SubAccountType.ELIGIBLE_SUB_ACCOUNT_TYPE_CODES.toString());
280             success &= false;
281         }
282 
283         // get a convenience reference to this code
284         String cgA21TypeCode = a21.getSubAccountTypeCode();
285 
286         // if this is a Cost Sharing SubAccount, run the Cost Sharing rules
287         if (OLEConstants.SubAccountType.COST_SHARE.trim().equalsIgnoreCase(StringUtils.trim(cgA21TypeCode))) {
288             success &= checkCgCostSharingRules();
289         }
290 
291         // if this is an ICR subaccount, run the ICR rules
292         if (OLEConstants.SubAccountType.EXPENSE.trim().equals(StringUtils.trim(cgA21TypeCode))) {
293             success &= checkCgIcrRules();
294         }
295 
296         return success;
297     }
298 
299     /**
300      * This checks that if the cost share information is filled out that it is valid and exists, or if fields are missing (such as
301      * the chart of accounts code and account number) an error is recorded
302      * 
303      * @return true if all cost share fields filled out correctly, false if the chart of accounts code and account number for cost
304      *         share are missing
305      */
306     protected boolean checkCgCostSharingRules() {
307 
308         boolean success = true;
309         boolean allFieldsSet = false;
310 
311         A21SubAccount a21 = newSubAccount.getA21SubAccount();
312 
313         // check to see if all required fields are set
314         if (StringUtils.isNotEmpty(a21.getCostShareChartOfAccountCode()) && StringUtils.isNotEmpty(a21.getCostShareSourceAccountNumber())) {
315             allFieldsSet = true;
316         }
317 
318         // Cost Sharing COA Code and Cost Sharing Account Number are required
319         success &= checkEmptyBOField("a21SubAccount.costShareChartOfAccountCode", a21.getCostShareChartOfAccountCode(), "Cost Share Chart of Accounts Code");
320         success &= checkEmptyBOField("a21SubAccount.costShareSourceAccountNumber", a21.getCostShareSourceAccountNumber(), "Cost Share AccountNumber");
321 
322         // existence test on Cost Share Account
323         if (allFieldsSet) {
324             if (ObjectUtils.isNull(a21.getCostShareAccount())) {
325                 putFieldError("a21SubAccount.costShareSourceAccountNumber", OLEKeyConstants.ERROR_EXISTENCE, getDisplayName("a21SubAccount.costShareSourceAccountNumber"));
326                 success &= false;
327             }
328         }
329 
330         // existence test on Cost Share SubAccount
331         if (allFieldsSet && StringUtils.isNotBlank(a21.getCostShareSourceSubAccountNumber())) {
332             if (ObjectUtils.isNull(a21.getCostShareSourceSubAccount())) {
333                 putFieldError("a21SubAccount.costShareSourceSubAccountNumber", OLEKeyConstants.ERROR_EXISTENCE, getDisplayName("a21SubAccount.costShareSourceSubAccountNumber"));
334                 success &= false;
335             }
336         }
337 
338         // Cost Sharing Account may not be for contracts and grants
339         if (ObjectUtils.isNotNull(a21.getCostShareAccount())) {
340             if (ObjectUtils.isNotNull(a21.getCostShareAccount().getSubFundGroup())) {
341                 if (a21.getCostShareAccount().isForContractsAndGrants()) {
342                     putFieldError("a21SubAccount.costShareSourceAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_COST_SHARE_ACCOUNT_MAY_NOT_BE_CG_FUNDGROUP, new String[] { SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingAttributeLabel(), SpringContext.getBean(SubFundGroupService.class).getContractsAndGrantsDenotingValueForMessage() });
343                     success &= false;
344                 }
345             }
346         }
347 
348         // The ICR fields must be empty if the sub-account type code is for cost sharing
349         if (checkCgIcrIsEmpty() == false) {
350             putFieldError("a21SubAccount.indirectCostRecoveryTypeCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_ICR_SECTION_INVALID, a21.getSubAccountTypeCode());
351             success &= false;
352         }
353 
354         return success;
355     }
356 
357     /**
358      * This checks that if the ICR information is entered that it is valid for this fiscal year and that all of its fields are valid
359      * as well (such as account)
360      * 
361      * @return true if the ICR information is filled in and it is valid
362      */
363     protected boolean checkCgIcrRules() {
364         A21SubAccount a21 = newSubAccount.getA21SubAccount();
365         if(ObjectUtils.isNull(a21)) {
366             return true;
367         }
368 
369         boolean success = true;
370         
371         // existence check for ICR Type Code
372         if (StringUtils.isNotEmpty(a21.getIndirectCostRecoveryTypeCode())) {
373             if (ObjectUtils.isNull(a21.getIndirectCostRecoveryType())) {
374                 putFieldError("a21SubAccount.indirectCostRecoveryTypeCode", OLEKeyConstants.ERROR_EXISTENCE, "ICR Type Code: " + a21.getIndirectCostRecoveryTypeCode());
375                 success = false;
376             }
377         }
378 
379         // existence check for Financial Series ID
380         if (StringUtils.isNotEmpty(a21.getFinancialIcrSeriesIdentifier())) {            
381             String fiscalYear = StringUtils.EMPTY + SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
382             String icrSeriesId = a21.getFinancialIcrSeriesIdentifier();
383             
384             Map<String, String> pkMap = new HashMap<String, String>();
385             pkMap.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear);
386             pkMap.put(OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, icrSeriesId);
387             Collection<IndirectCostRecoveryRateDetail> icrRateDetails = getBoService().findMatching(IndirectCostRecoveryRateDetail.class, pkMap);
388             
389             if (ObjectUtils.isNull(icrRateDetails) || icrRateDetails.isEmpty()) {
390                 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(A21SubAccount.class, OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER);
391                 putFieldError(OLEPropertyConstants.A21_SUB_ACCOUNT + "." + OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, OLEKeyConstants.ERROR_EXISTENCE, label + " (" + icrSeriesId + ")");
392                 success = false;
393             }
394             else {
395                 for(IndirectCostRecoveryRateDetail icrRateDetail : icrRateDetails) {
396                     if(ObjectUtils.isNull(icrRateDetail.getIndirectCostRecoveryRate())){                                
397                         putFieldError(OLEPropertyConstants.A21_SUB_ACCOUNT + "." + OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, OLEKeyConstants.IndirectCostRecovery.ERROR_DOCUMENT_ICR_RATE_NOT_FOUND, new String[]{fiscalYear, icrSeriesId});
398                         success = false;
399                         break;
400                     }
401                 }
402             }            
403         }
404 
405         // existence check for ICR Account
406         for (A21IndirectCostRecoveryAccount account : a21.getA21ActiveIndirectCostRecoveryAccounts()){
407             if (StringUtils.isNotBlank(account.getIndirectCostRecoveryAccountNumber())
408                 && StringUtils.isNotBlank(account.getIndirectCostRecoveryFinCoaCode())){
409                 if(ObjectUtils.isNull(account.getIndirectCostRecoveryAccount())){                                
410                     putFieldError(OLEPropertyConstants.A21INDIRECT_COST_RECOVERY_ACCOUNTS, OLEKeyConstants.ERROR_EXISTENCE, "ICR Account: " + account.getIndirectCostRecoveryFinCoaCode() + "-" + account.getIndirectCostRecoveryAccountNumber());
411                     success = false;
412                     break;
413                 }
414             }
415         }
416 
417         // The cost sharing fields must be empty if the sub-account type code is for ICR
418         if (checkCgCostSharingIsEmpty() == false) {
419             putFieldError("a21SubAccount.costShareChartOfAccountCode", OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_COST_SHARE_SECTION_INVALID, a21.getSubAccountTypeCode());
420 
421             success &= false;
422         }
423 
424         return success;
425     }
426 
427     /**
428      * This method tests if all fields in the Cost Sharing section are empty.
429      * 
430      * @return true if the cost sharing values passed in are empty, otherwise false.
431      */
432     protected boolean checkCgCostSharingIsEmpty() {
433         boolean success = true;
434 
435         A21SubAccount newA21SubAccount = newSubAccount.getA21SubAccount();
436         if (ObjectUtils.isNotNull(newA21SubAccount)) {
437             success &= StringUtils.isEmpty(newA21SubAccount.getCostShareChartOfAccountCode());
438             success &= StringUtils.isEmpty(newA21SubAccount.getCostShareSourceAccountNumber());
439             success &= StringUtils.isEmpty(newA21SubAccount.getCostShareSourceSubAccountNumber());
440         }
441 
442         return success;
443     }
444 
445     /**
446      * This method tests if all fields in the ICR section are empty.
447      * 
448      * @return true if the ICR values passed in are empty, otherwise false.
449      */
450     protected boolean checkCgIcrIsEmpty() {
451         boolean success = true;
452         
453         A21SubAccount newA21SubAccount = newSubAccount.getA21SubAccount();
454         if (ObjectUtils.isNotNull(newA21SubAccount)) {
455             success &= StringUtils.isEmpty(newA21SubAccount.getFinancialIcrSeriesIdentifier());
456             
457             success &= checkICRCollectionExist(false);
458             success &= StringUtils.isEmpty(newA21SubAccount.getIndirectCostRecoveryTypeCode());
459             // this is a boolean, so create any value if set to true, meaning a user checked the box, otherwise assume it's empty
460             success &= StringUtils.isEmpty(newA21SubAccount.getOffCampusCode() ? "1" : "");
461         }
462 
463         return success;
464     }
465 
466     /**
467      * This method tests the value entered, and if there is anything there it logs a new error, and returns false.
468      * 
469      * @param value - String value to be tested
470      * @param fieldName - name of the field being tested
471      * @return false if there is any value in value, otherwise true
472      */
473     protected boolean disallowAnyValues(String value, String fieldName) {
474         if (StringUtils.isNotEmpty(value)) {
475             putFieldError(fieldName, OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NOT_AUTHORIZED_ENTER_CG_FIELDS, getDisplayName(fieldName));
476             return false;
477         }
478         return true;
479     }
480 
481     /**
482      * This method tests the two values entered, and if there is any change between the two, it logs an error, and returns false.
483      * Note that the comparison is done after trimming both leading and trailing whitespace from both strings, and then doing a
484      * case-insensitive comparison.
485      * 
486      * @param oldValue - the original String value of the field
487      * @param newValue - the new String value of the field
488      * @param fieldName - name of the field being tested
489      * @return false if there is any difference between the old and new, true otherwise
490      */
491     protected boolean disallowChangedValues(String oldValue, String newValue, String fieldName) {
492 
493         if (isFieldValueChanged(oldValue, newValue)) {
494             putFieldError(fieldName, OLEKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NOT_AUTHORIZED_CHANGE_CG_FIELDS, getDisplayName(fieldName));
495             return false;
496         }
497         return true;
498     }
499 
500     /**
501      * This compares two string values to see if the newValue has changed from the oldValue
502      * 
503      * @param oldValue - original value
504      * @param newValue - new value
505      * @return true if the two fields are different from each other
506      */
507     protected boolean isFieldValueChanged(String oldValue, String newValue) {
508 
509         if (StringUtils.isBlank(oldValue) && StringUtils.isBlank(newValue)) {
510             return false;
511         }
512 
513         if (StringUtils.isBlank(oldValue) && StringUtils.isNotBlank(newValue)) {
514             return true;
515         }
516 
517         if (StringUtils.isNotBlank(oldValue) && StringUtils.isBlank(newValue)) {
518             return true;
519         }
520 
521         if (!oldValue.trim().equalsIgnoreCase(newValue.trim())) {
522             return true;
523         }
524 
525         return false;
526     }
527 
528 
529     /**
530      * This method retrieves the label name for a specific property
531      * 
532      * @param propertyName - property to retrieve label for (from the DD)
533      * @return the label
534      */
535     protected String getDisplayName(String propertyName) {
536         return getDdService().getAttributeLabel(SubAccount.class, propertyName);
537     }
538 
539 }