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 java.sql.Timestamp; 019 import java.util.Calendar; 020 import java.util.Collection; 021 import java.util.HashMap; 022 import java.util.Map; 023 024 import org.apache.commons.lang.StringUtils; 025 import org.apache.commons.lang.time.DateUtils; 026 import org.kuali.kfs.coa.businessobject.Account; 027 import org.kuali.kfs.coa.businessobject.AccountDelegate; 028 import org.kuali.kfs.sys.KFSConstants; 029 import org.kuali.kfs.sys.KFSKeyConstants; 030 import org.kuali.kfs.sys.KFSPropertyConstants; 031 import org.kuali.kfs.sys.context.SpringContext; 032 import org.kuali.kfs.sys.document.service.FinancialSystemDocumentTypeService; 033 import org.kuali.rice.kim.bo.Person; 034 import org.kuali.rice.kns.document.MaintenanceDocument; 035 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 036 import org.kuali.rice.kns.util.KualiDecimal; 037 import org.kuali.rice.kns.util.ObjectUtils; 038 039 /** 040 * Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route 041 * event. 042 */ 043 public class DelegateRule extends MaintenanceDocumentRuleBase { 044 045 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DelegateRule.class); 046 047 protected AccountDelegate oldDelegate; 048 protected AccountDelegate newDelegate; 049 050 /** 051 * Constructs a DelegateRule.java. 052 */ 053 public DelegateRule() { 054 super(); 055 } 056 057 /** 058 * This runs specific rules that are called when a document is saved: 059 * <ul> 060 * <li>{@link DelegateRule#checkSimpleRules()}</li> 061 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li> 062 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li> 063 * </ul> 064 * 065 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 066 * @return doesn't fail on save, even if sub-rules fail 067 */ 068 @Override 069 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 070 071 LOG.info("Entering processCustomSaveDocumentBusinessRules()"); 072 setupConvenienceObjects(document); 073 074 // check simple rules 075 checkSimpleRules(); 076 077 // disallow more than one PrimaryRoute for a given Chart/Account/Doctype 078 checkOnlyOnePrimaryRoute(document); 079 080 // delegate user must be Active and Professional 081 checkDelegateUserRules(document); 082 083 return true; 084 } 085 086 /** 087 * This runs specific rules that are called when a document is routed: 088 * <ul> 089 * <li>{@link DelegateRule#checkSimpleRules()}</li> 090 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li> 091 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li> 092 * </ul> 093 * 094 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 095 * @return fails if sub-rules fail 096 */ 097 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 098 LOG.info("Entering processCustomRouteDocumentBusinessRules()"); 099 100 setupConvenienceObjects(document); 101 102 // check simple rules 103 boolean success = checkSimpleRules(); 104 105 // disallow more than one PrimaryRoute for a given Chart/Account/Doctype 106 success &= checkOnlyOnePrimaryRoute(document); 107 108 // delegate user must be Active and Professional 109 success &= checkDelegateUserRules(document); 110 111 return success; 112 } 113 114 /** 115 * This runs specific rules that are called when a document is approved: 116 * <ul> 117 * <li>{@link DelegateRule#checkSimpleRules()}</li> 118 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li> 119 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li> 120 * </ul> 121 * 122 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 123 */ 124 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { 125 126 boolean success = true; 127 128 LOG.info("Entering processCustomApproveDocumentBusinessRules()"); 129 setupConvenienceObjects(document); 130 131 // check simple rules 132 success &= checkSimpleRules(); 133 134 success &= checkOnlyOnePrimaryRoute(document); 135 136 // delegate user must be Active and Professional 137 success &= checkDelegateUserRules(document); 138 139 return success; 140 } 141 142 /** 143 * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and 144 * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load 145 * all sub-objects from the DB by their primary keys, if available. 146 * 147 * @param document - the maintenanceDocument being evaluated 148 */ 149 protected void setupConvenienceObjects(MaintenanceDocument document) { 150 151 // setup oldAccount convenience objects, make sure all possible sub-objects are populated 152 oldDelegate = (AccountDelegate) super.getOldBo(); 153 154 // setup newAccount convenience objects, make sure all possible sub-objects are populated 155 newDelegate = (AccountDelegate) super.getNewBo(); 156 } 157 158 159 /** 160 * This checks to see if 161 * <ul> 162 * <li>the delegate start date is valid and they are active</li> 163 * <li>from amount is >= 0</li> 164 * <li>to amount cannot be empty when from amount is filled out</li> 165 * <li>to amount is >= from amount</li> 166 * <li>account cannot be closed</li> 167 * </ul> 168 * 169 * @return 170 */ 171 protected boolean checkSimpleRules() { 172 boolean success = true; 173 174 Map<String, String> fieldValues = new HashMap<String, String>(); 175 fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, newDelegate.getChartOfAccountsCode()); 176 fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, newDelegate.getAccountNumber()); 177 178 int accountExist = getBoService().countMatching(Account.class, fieldValues); 179 if (accountExist<=0) { 180 putFieldError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, newDelegate.getAccountNumber()); 181 success &= false; 182 } 183 184 // start date must be greater than or equal to today if active 185 boolean newActive = newDelegate.isActive(); 186 if ((ObjectUtils.isNotNull(newDelegate.getAccountDelegateStartDate())) && newActive) { 187 Timestamp today = getDateTimeService().getCurrentTimestamp(); 188 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); 189 if (newDelegate.getAccountDelegateStartDate().before(today)) { 190 putFieldError(KFSPropertyConstants.ACCOUNT_DELEGATE_START_DATE, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_STARTDATE_IN_PAST); 191 success &= false; 192 } 193 } 194 195 // FROM amount must be >= 0 (may not be negative) 196 KualiDecimal fromAmount = newDelegate.getFinDocApprovalFromThisAmt(); 197 if (ObjectUtils.isNotNull(fromAmount)) { 198 if (fromAmount.isLessThan(KualiDecimal.ZERO)) { 199 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_FROM_THIS_AMT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE); 200 success &= false; 201 } 202 } 203 204 // TO amount must be >= FROM amount or Zero 205 KualiDecimal toAmount = newDelegate.getFinDocApprovalToThisAmount(); 206 if (ObjectUtils.isNotNull(toAmount) && !toAmount.equals(KualiDecimal.ZERO)) { 207 // case if FROM amount is non-null and positive, disallow TO amount being less 208 if (fromAmount != null && toAmount.isLessThan(fromAmount)) { 209 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO); 210 success &= false; 211 } else if (toAmount.isLessThan(KualiDecimal.ZERO)) { 212 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO); 213 success &= false; 214 } 215 } 216 217 // do we have a good document type? 218 final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class); 219 if (!documentService.isFinancialSystemDocumentType(newDelegate.getFinancialDocumentTypeCode())) { 220 putFieldError(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { newDelegate.getFinancialDocumentTypeCode(), KFSConstants.ROOT_DOCUMENT_TYPE }); 221 success = false; 222 } 223 224 return success; 225 } 226 227 /** 228 * This checks to see if there is already a record for the primary route 229 * 230 * @param document 231 * @return false if there is a record 232 */ 233 protected boolean checkOnlyOnePrimaryRoute(MaintenanceDocument document) { 234 235 boolean success = true; 236 boolean checkDb = false; 237 boolean newPrimary; 238 boolean newActive; 239 boolean blockingDocumentExists; 240 241 // exit out immediately if this doc is not requesting a primary route 242 newPrimary = newDelegate.isAccountsDelegatePrmrtIndicator(); 243 if (!newPrimary) { 244 return success; 245 } 246 247 // exit if new document not active 248 newActive = newDelegate.isActive(); 249 if (!newActive) { 250 return success; 251 } 252 253 // if its a new document, we are only interested if they have chosen this one 254 // to be a primary route 255 if (document.isNew()) { 256 if (newPrimary) { 257 checkDb = true; 258 } 259 } 260 261 // handle an edit, where all we care about is that someone might change it 262 // from NOT a primary TO a primary 263 if (document.isEdit()) { 264 boolean oldPrimary = oldDelegate.isAccountsDelegatePrmrtIndicator(); 265 if (!oldPrimary && newPrimary) { 266 checkDb = true; 267 } 268 } 269 270 // if we dont want to check the db for another primary, then exit 271 if (!checkDb) { 272 return success; 273 } 274 275 // if a primary already exists for a document type (including ALL), throw an error. However, current business rules 276 // should allow a primary for other document types, even if a primary for ALL already exists. 277 278 Map whereMap = new HashMap(); 279 whereMap.put("chartOfAccountsCode", newDelegate.getChartOfAccountsCode()); 280 whereMap.put("accountNumber", newDelegate.getAccountNumber()); 281 whereMap.put("accountsDelegatePrmrtIndicator", Boolean.valueOf(true)); 282 whereMap.put("financialDocumentTypeCode", newDelegate.getFinancialDocumentTypeCode()); 283 whereMap.put("active", Boolean.valueOf(true)); 284 285 // find all the matching records 286 Collection primaryRoutes = getBoService().findMatching(AccountDelegate.class, whereMap); 287 288 // if there is at least one result, then this business rule is tripped 289 if (primaryRoutes.size() > 0) { 290 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE); 291 success &= false; 292 } 293 294 return success; 295 } 296 297 /** 298 * This checks to see if the user is valid and active for this module 299 * 300 * @param document 301 * @return false if this user is not valid or active for being a delegate 302 */ 303 protected boolean checkDelegateUserRules(MaintenanceDocument document) { 304 boolean success = true; 305 final Person accountDelegate = newDelegate.getAccountDelegate(); 306 307 // if the user doesn't exist, then do nothing, it'll fail the existence test elsewhere 308 if (ObjectUtils.isNull(accountDelegate)) { 309 return success; 310 } 311 312 // if the document is inactivating an account delegate, don't bother validating the user 313 if (document.getOldMaintainableObject() != null && document.getOldMaintainableObject().getBusinessObject() != null && ((AccountDelegate)document.getOldMaintainableObject().getBusinessObject()).isActive() && !((AccountDelegate)document.getNewMaintainableObject().getBusinessObject()).isActive()) { 314 return success; 315 } 316 317 if (StringUtils.isBlank(accountDelegate.getEntityId()) || !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE, accountDelegate.getPrincipalId())) { 318 super.putFieldError("accountDelegate.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountDelegate.getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE}); 319 success = false; 320 } 321 return success; 322 } 323 }