001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.coa.document.validation.impl; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.kfs.coa.businessobject.AccountDelegateModel; 020 import org.kuali.kfs.coa.businessobject.AccountDelegateModelDetail; 021 import org.kuali.kfs.sys.KFSConstants; 022 import org.kuali.kfs.sys.KFSKeyConstants; 023 import org.kuali.kfs.sys.context.SpringContext; 024 import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService; 025 import org.kuali.kfs.sys.document.service.FinancialSystemDocumentTypeService; 026 import org.kuali.rice.kns.bo.PersistableBusinessObject; 027 import org.kuali.rice.kns.document.MaintenanceDocument; 028 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 029 import org.kuali.rice.kns.util.GlobalVariables; 030 import org.kuali.rice.kns.util.KualiDecimal; 031 import org.kuali.rice.kns.util.ObjectUtils; 032 033 /** 034 * This class implements the business rules specific to the {@link OrganizationRoutingModelName} Maintenance Document. 035 */ 036 public class AccountDelegateModelRule extends MaintenanceDocumentRuleBase { 037 038 private AccountDelegateModel model; 039 040 /** 041 * Constructs a AccountDelegateModelRule 042 */ 043 public AccountDelegateModelRule() { 044 } 045 046 /** 047 * This method sets the convenience objects like model, so you have short and easy handles to the new and 048 * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load 049 * all sub-objects from the DB by their primary keys, if available. 050 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupConvenienceObjects() 051 */ 052 @Override 053 public void setupConvenienceObjects() { 054 model = (AccountDelegateModel) super.getNewBo(); 055 for (AccountDelegateModelDetail delegateModel : model.getAccountDelegateModelDetails()) { 056 delegateModel.refreshNonUpdateableReferences(); 057 } 058 } 059 060 /** 061 * This performs rules checks on document approve 062 * <ul> 063 * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li> 064 * </ul> 065 * This rule fails on business rule failures 066 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 067 */ 068 @Override 069 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { 070 setupConvenienceObjects(); 071 return checkSimpleRules(document, this.model); 072 } 073 074 /** 075 * This performs rules checks on document route 076 * <ul> 077 * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li> 078 * </ul> 079 * This rule fails on business rule failures 080 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 081 */ 082 @Override 083 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 084 setupConvenienceObjects(); 085 return checkSimpleRules(document, this.model); 086 } 087 088 /** 089 * This performs rules checks on document save 090 * <ul> 091 * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li> 092 * </ul> 093 * This rule does not fail on business rule failures 094 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 095 */ 096 @Override 097 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 098 setupConvenienceObjects(); 099 checkSimpleRules(document, this.model); 100 return true; 101 } 102 103 /** 104 * This method calls 105 * <ul> 106 * <li>{@link AccountDelegateModelRule#checkSimpleRulesForOrganizationRoutingModel(OrganizationRoutingModelName, OrganizationRoutingModel)}</li> 107 * </ul> 108 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, 109 * java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject) 110 */ 111 @Override 112 public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) { 113 setupConvenienceObjects(); 114 final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class); 115 return checkSimpleRulesForOrganizationRoutingModel(document, this.model, (AccountDelegateModelDetail) line, documentService); 116 } 117 118 /** 119 * Checks the given rules against the entire Organization Routing Model parent. 120 * 121 * @param globalDelegateTemplate the Organization Routing Model parent to check 122 * @return true if document passes all rules, false if otherwise 123 */ 124 protected boolean checkSimpleRules(MaintenanceDocument document, AccountDelegateModel globalDelegateTemplate) { 125 boolean success = true; 126 127 success &= checkModelNameHasAtLeastOneModel(globalDelegateTemplate); 128 129 int line = 0; 130 final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class); 131 for (AccountDelegateModelDetail delegateModel : globalDelegateTemplate.getAccountDelegateModelDetails()) { 132 GlobalVariables.getMessageMap().addToErrorPath(MAINTAINABLE_ERROR_PATH + ".accountDelegateModelDetails[" + line + "]."); 133 success &= checkSimpleRulesForOrganizationRoutingModel(document, globalDelegateTemplate, delegateModel, documentService); 134 GlobalVariables.getMessageMap().addToErrorPath(MAINTAINABLE_ERROR_PATH + ".accountDelegateModelDetails[" + line + "]."); 135 line++; 136 } 137 return success; 138 } 139 140 /** 141 * This method checks a series of basic rules for a single org routing model. 142 * 143 * @return true if model passes all the checks, false if otherwise 144 */ 145 protected boolean checkSimpleRulesForOrganizationRoutingModel(MaintenanceDocument document, AccountDelegateModel globalDelegateTemplate, AccountDelegateModelDetail delegateModel, FinancialSystemDocumentTypeService documentService) { 146 boolean success = true; 147 148 if (delegateModel.isActive()) { 149 success &= checkDelegateFromAmountPositive(delegateModel); 150 success &= checkDelegateToAmountGreaterThanFromAmount(delegateModel); 151 success &= checkDelegateUserRules(document, delegateModel); 152 success &= checkPrimaryRoutePerDocType(globalDelegateTemplate, delegateModel); 153 success &= checkDelegateDocumentTypeCode(delegateModel.getFinancialDocumentTypeCode(), documentService); 154 } 155 156 return success; 157 } 158 159 /** 160 * This method makes certain that the collection of account delegates in the "mo itdel" has at least one account delegate 161 * template in it. 162 * 163 * @param globalDelegateTemplate the account delegate model to check 164 * @return true if account delegate model has at least one account delegate template in it 165 */ 166 protected boolean checkModelNameHasAtLeastOneModel(AccountDelegateModel globalDelegateTemplate) { 167 boolean success = true; 168 if (globalDelegateTemplate.getAccountDelegateModelDetails().size() == 0) { 169 success = false; 170 GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "add.accountDelegateModelDetails.financialDocumentTypeCode", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_DELEGATE, new String[0]); 171 } 172 return success; 173 } 174 175 /** 176 * This method checks that the account delegate model has at least one active "model" within it. 177 * 178 * @param globalDelegateTemplate the account delegate model to check 179 * @return true if account delegate model has at least one active model in it. 180 */ 181 // method not currently in use, as per Bill's comments in KULRNE-4805 182 protected boolean checkModelNameHasAtLeastOneActiveModel(AccountDelegateModel globalDelegateTemplate) { 183 boolean success = true; 184 int activeModelCount = 0; 185 186 for (AccountDelegateModelDetail mdl : globalDelegateTemplate.getAccountDelegateModelDetails()) { 187 if (mdl.isActive()) { 188 activeModelCount++; 189 } 190 } 191 192 if (activeModelCount == 0) { 193 success = false; 194 if (globalDelegateTemplate.getAccountDelegateModelDetails().size() == 0) { 195 GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "add.accountDelegateModelDetails.active", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_ACTIVE_DELEGATE, new String[0]); 196 } 197 else { 198 GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "accountDelegateModelDetails[0].active", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_ACTIVE_DELEGATE, new String[0]); 199 } 200 } 201 return success; 202 } 203 204 /** 205 * Checks that if approval from amount is not null, then it is positive 206 * 207 * @param delegateModel Organization Routing Model to check 208 * @return true if Organization Routing Model passes the checks, false if otherwise 209 */ 210 protected boolean checkDelegateFromAmountPositive(AccountDelegateModelDetail delegateModel) { 211 boolean result = true; 212 if (!ObjectUtils.isNull(delegateModel.getApprovalFromThisAmount())) { 213 if (delegateModel.getApprovalFromThisAmount().isLessThan(KualiDecimal.ZERO)) { 214 GlobalVariables.getMessageMap().putError("approvalFromThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE, new String[0]); 215 result = false; 216 } 217 } 218 return result; 219 } 220 221 /** 222 * Checks that if approval from amount is null, that approval to this amount is null or zero; and then checks that approval to 223 * amount is greater than or equal to approval from amount. 224 * 225 * @param delegateModel Organization Routing Model to check 226 * @return true if the Organization Routing Model passes the checks, false if otherwise 227 */ 228 protected boolean checkDelegateToAmountGreaterThanFromAmount(AccountDelegateModelDetail delegateModel) { 229 boolean result = true; 230 if (!ObjectUtils.isNull(delegateModel.getApprovalFromThisAmount())) { 231 if (!ObjectUtils.isNull(delegateModel.getApprovalToThisAmount())) { 232 if (delegateModel.getApprovalToThisAmount().isLessThan(delegateModel.getApprovalFromThisAmount())) { 233 GlobalVariables.getMessageMap().putError("approvalToThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO, new String[0]); 234 result = false; 235 } 236 } 237 } 238 if (!ObjectUtils.isNull(delegateModel.getApprovalToThisAmount()) && delegateModel.getApprovalToThisAmount().isLessThan(KualiDecimal.ZERO)) { 239 GlobalVariables.getMessageMap().putError("approvalToThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO, new String[0]); 240 result = false; 241 } 242 return result; 243 } 244 245 /** 246 * Checks that the account delegate listed exists in the system, and that user has an active status and is a professional type 247 * 248 * @param delegateModel the Organization Routing Model to check 249 * @return true if delegate user passes the rules described above; false if they fail 250 */ 251 protected boolean checkDelegateUserRules(MaintenanceDocument document, AccountDelegateModelDetail delegateModel) { 252 boolean success = true; 253 254 // refresh account delegate 255 try { 256 delegateModel.setAccountDelegate(SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPerson(delegateModel.getAccountDelegateUniversalId())); 257 } 258 catch (Exception e) { 259 if (LOG.isDebugEnabled()) { 260 LOG.debug("User Not Found Exception: " + e); 261 } 262 } 263 264 // user must exist 265 if (delegateModel.getAccountDelegate() == null) { 266 GlobalVariables.getMessageMap().putError("accountDelegate.principalName", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_DOESNT_EXIST, new String[0]); 267 success = false; 268 } 269 270 if (success) { 271 if (!getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE, delegateModel.getAccountDelegate().getPrincipalId())) { 272 super.putFieldError("accountDelegate.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {delegateModel.getAccountDelegate().getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE}); 273 success = false; 274 } 275 } 276 277 return success; 278 } 279 280 /** 281 * This method validates the rule that says there can be only one PrimaryRoute delegate for each given docType. It checks the 282 * delegateGlobalToTest against the list, to determine whether adding this new delegateGlobalToTest would violate any 283 * PrimaryRoute business rule violations. If any of the incoming variables is null or empty, the method will do nothing, and 284 * return Null. It will only process the business rules if there is sufficient data to do so. 285 * 286 * @param delegateGlobalToTest A delegateGlobal line that you want to test against the list. 287 * @param delegateGlobals A List of delegateGlobal items that is being tested against. 288 * @return true if model, delegate template or org routing model is null, or if the primary routing indicator is set to false or the doc type code is empty 289 * otherwise it checks to make sure that there is indeed one model marked as the primary route 290 */ 291 protected boolean checkPrimaryRoutePerDocType(AccountDelegateModel globalDelegateTemplate, AccountDelegateModelDetail delegateModel) { 292 boolean success = true; 293 294 // exit immediately if the adding line isnt a Primary routing 295 if (delegateModel == null || globalDelegateTemplate == null || globalDelegateTemplate.getAccountDelegateModelDetails().isEmpty()) { 296 return success; 297 } 298 if (!delegateModel.getAccountDelegatePrimaryRoutingIndicator()) { 299 return success; 300 } 301 if (StringUtils.isBlank(delegateModel.getFinancialDocumentTypeCode())) { 302 return success; 303 } 304 305 // at this point, the delegateGlobal being added is a Primary for ALL docTypes, so we need to 306 // test whether any in the existing list are also Primary, regardless of docType 307 String docType = delegateModel.getFinancialDocumentTypeCode(); 308 for (AccountDelegateModelDetail currDelegateModel : globalDelegateTemplate.getAccountDelegateModelDetails()) { 309 if (currDelegateModel.isActive() && !delegateModel.equals(currDelegateModel) && currDelegateModel.getAccountDelegatePrimaryRoutingIndicator() && delegateModel.getFinancialDocumentTypeCode().equals(currDelegateModel.getFinancialDocumentTypeCode())) { 310 success = false; 311 GlobalVariables.getMessageMap().putError("accountDelegatePrimaryRoutingIndicator", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_DELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE, new String[0]); 312 } 313 } 314 315 return success; 316 } 317 318 /** 319 * Validates the document type code for the delegate, to make sure it is a Financial System document type code 320 * @param documentTypeCode the document type code to check 321 * @param delegateService a helpful instance of the delegate service, so new ones don't have to be created all the time 322 * @return true if the document type code is valid, false otherwise 323 */ 324 protected boolean checkDelegateDocumentTypeCode(String documentTypeCode, FinancialSystemDocumentTypeService documentService) { 325 if (!documentService.isFinancialSystemDocumentType(documentTypeCode)) { 326 putFieldError("financialDocumentTypeCode", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { documentTypeCode, KFSConstants.ROOT_DOCUMENT_TYPE }); 327 return false; 328 } 329 return true; 330 } 331 332 } 333