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.sql.Timestamp;
19  import java.util.Calendar;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.commons.lang.time.DateUtils;
26  import org.kuali.ole.coa.businessobject.Account;
27  import org.kuali.ole.coa.businessobject.AccountDelegate;
28  import org.kuali.ole.sys.OLEConstants;
29  import org.kuali.ole.sys.OLEKeyConstants;
30  import org.kuali.ole.sys.OLEPropertyConstants;
31  import org.kuali.ole.sys.context.SpringContext;
32  import org.kuali.ole.sys.document.service.FinancialSystemDocumentTypeService;
33  import org.kuali.ole.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;
34  import org.kuali.rice.core.api.util.type.KualiDecimal;
35  import org.kuali.rice.kim.api.identity.Person;
36  import org.kuali.rice.kns.document.MaintenanceDocument;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  
39  /**
40   * Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route
41   * event.
42   */
43  public class DelegateRule extends KfsMaintenanceDocumentRuleBase {
44  
45      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DelegateRule.class);
46  
47      protected AccountDelegate oldDelegate;
48      protected AccountDelegate newDelegate;
49  
50      /**
51       * This runs specific rules that are called when a document is saved:
52       * <ul>
53       * <li>{@link DelegateRule#checkSimpleRules()}</li>
54       * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
55       * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
56       * </ul>
57       * 
58       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
59       * @return doesn't fail on save, even if sub-rules fail
60       */
61      @Override
62      protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
63  
64          LOG.debug("Entering processCustomSaveDocumentBusinessRules()");
65          setupConvenienceObjects(document);
66  
67          // check simple rules
68          checkSimpleRules();
69  
70          // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
71          checkOnlyOnePrimaryRoute(document);
72  
73          // delegate user must be Active and Professional
74          checkDelegateUserRules(document);
75  
76          return true;
77      }
78  
79      /**
80       * This runs specific rules that are called when a document is routed:
81       * <ul>
82       * <li>{@link DelegateRule#checkSimpleRules()}</li>
83       * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
84       * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
85       * </ul>
86       * 
87       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
88       * @return fails if sub-rules fail
89       */
90      @Override
91      protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
92          LOG.debug("Entering processCustomRouteDocumentBusinessRules()");
93  
94          setupConvenienceObjects(document);
95  
96          // check simple rules
97          boolean success = checkSimpleRules();
98          
99          // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
100         success &= checkOnlyOnePrimaryRoute(document);
101 
102         // delegate user must be Active and Professional
103         success &= checkDelegateUserRules(document);
104         
105         return success;
106     }
107 
108     /**
109      * This runs specific rules that are called when a document is approved:
110      * <ul>
111      * <li>{@link DelegateRule#checkSimpleRules()}</li>
112      * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
113      * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
114      * </ul>
115      * 
116      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
117      */
118     @Override
119     protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
120 
121         boolean success = true;
122 
123         LOG.debug("Entering processCustomApproveDocumentBusinessRules()");
124         setupConvenienceObjects(document);
125 
126         // check simple rules
127         success &= checkSimpleRules();
128 
129         success &= checkOnlyOnePrimaryRoute(document);
130 
131         // delegate user must be Active and Professional
132         success &= checkDelegateUserRules(document);
133 
134         return success;
135     }
136 
137     /**
138      * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
139      * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
140      * all sub-objects from the DB by their primary keys, if available.
141      * 
142      * @param document - the maintenanceDocument being evaluated
143      */
144     protected void setupConvenienceObjects(MaintenanceDocument document) {
145 
146         // setup oldAccount convenience objects, make sure all possible sub-objects are populated
147         oldDelegate = (AccountDelegate) super.getOldBo();
148 
149         // setup newAccount convenience objects, make sure all possible sub-objects are populated
150         newDelegate = (AccountDelegate) super.getNewBo();
151     }
152 
153 
154     /**
155      * This checks to see if
156      * <ul>
157      * <li>the delegate start date is valid and they are active</li>
158      * <li>from amount is >= 0</li>
159      * <li>to amount cannot be empty when from amount is filled out</li>
160      * <li>to amount is >= from amount</li>
161      * <li>account cannot be closed</li>
162      * </ul>
163      * 
164      * @return
165      */
166     protected boolean checkSimpleRules() {
167         boolean success = true;
168 
169         Map<String, String> fieldValues = new HashMap<String, String>();
170         fieldValues.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, newDelegate.getChartOfAccountsCode());
171         fieldValues.put(OLEPropertyConstants.ACCOUNT_NUMBER, newDelegate.getAccountNumber());
172         
173         int accountExist = getBoService().countMatching(Account.class, fieldValues);
174         if (accountExist<=0) {
175             putFieldError(OLEPropertyConstants.ACCOUNT_NUMBER, OLEKeyConstants.ERROR_EXISTENCE, newDelegate.getAccountNumber());
176             success &= false;
177         }
178 
179         // start date must be greater than or equal to today if active
180         boolean newActive = newDelegate.isActive();
181         if ((ObjectUtils.isNotNull(newDelegate.getAccountDelegateStartDate())) && newActive) {
182             Timestamp today = getDateTimeService().getCurrentTimestamp();
183             today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime());
184             if (newDelegate.getAccountDelegateStartDate().before(today)) {
185                 putFieldError(OLEPropertyConstants.ACCOUNT_DELEGATE_START_DATE, OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_STARTDATE_IN_PAST);
186                 success &= false;
187             }
188         }
189 
190         // FROM amount must be >= 0 (may not be negative)
191         KualiDecimal fromAmount = newDelegate.getFinDocApprovalFromThisAmt();
192         if (ObjectUtils.isNotNull(fromAmount)) {
193             if (fromAmount.isLessThan(KualiDecimal.ZERO)) {
194                 putFieldError(OLEPropertyConstants.FIN_DOC_APPROVAL_FROM_THIS_AMT, OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
195                 success &= false;
196             }
197         }
198 
199         // TO amount must be >= FROM amount or Zero
200         KualiDecimal toAmount = newDelegate.getFinDocApprovalToThisAmount();
201         if (ObjectUtils.isNotNull(toAmount) && !toAmount.equals(KualiDecimal.ZERO)) {
202             // case if FROM amount is non-null and positive, disallow TO amount being less
203             if (fromAmount != null && toAmount.isLessThan(fromAmount)) {
204                 putFieldError(OLEPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
205                 success &= false;
206             } else if (toAmount.isLessThan(KualiDecimal.ZERO)) {
207                 putFieldError(OLEPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
208                 success &= false;
209             }
210         }
211         
212         // do we have a good document type?
213         final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
214         if (!documentService.isFinancialSystemDocumentType(newDelegate.getFinancialDocumentTypeCode())) {
215             putFieldError(OLEPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { newDelegate.getFinancialDocumentTypeCode(), OLEConstants.ROOT_DOCUMENT_TYPE });
216             success = false;
217         }
218 
219         return success;
220     }
221 
222     /**
223      * This checks to see if there is already a record for the primary route
224      * 
225      * @param document
226      * @return false if there is a record
227      */
228     protected boolean checkOnlyOnePrimaryRoute(MaintenanceDocument document) {
229 
230         boolean success = true;
231         boolean checkDb = false;
232         boolean newPrimary;
233         boolean newActive;
234         boolean blockingDocumentExists;
235 
236         // exit out immediately if this doc is not requesting a primary route
237         newPrimary = newDelegate.isAccountsDelegatePrmrtIndicator();
238         if (!newPrimary) {
239             return success;
240         }
241 
242         // exit if new document not active
243         newActive = newDelegate.isActive();
244         if (!newActive) {
245             return success;
246         }
247 
248         // if its a new document, we are only interested if they have chosen this one
249         // to be a primary route
250         if (document.isNew()) {
251             if (newPrimary) {
252                 checkDb = true;
253             }
254         }
255 
256         // handle an edit, where all we care about is that someone might change it
257         // from NOT a primary TO a primary
258         if (document.isEdit()) {
259             boolean oldPrimary = oldDelegate.isAccountsDelegatePrmrtIndicator();
260             if (!oldPrimary && newPrimary) {
261                 checkDb = true;
262             }
263         }
264 
265         // if we dont want to check the db for another primary, then exit
266         if (!checkDb) {
267             return success;
268         }
269 
270         // if a primary already exists for a document type (including ALL), throw an error. However, current business rules
271         // should allow a primary for other document types, even if a primary for ALL already exists.
272 
273         Map whereMap = new HashMap();
274         whereMap.put("chartOfAccountsCode", newDelegate.getChartOfAccountsCode());
275         whereMap.put("accountNumber", newDelegate.getAccountNumber());
276         whereMap.put("accountsDelegatePrmrtIndicator", Boolean.TRUE);
277         whereMap.put("financialDocumentTypeCode", newDelegate.getFinancialDocumentTypeCode());
278         whereMap.put("active", Boolean.TRUE);
279 
280         // find all the matching records
281         Collection primaryRoutes = getBoService().findMatching(AccountDelegate.class, whereMap);
282 
283         // if there is at least one result, then this business rule is tripped
284         if (primaryRoutes.size() > 0) {
285             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
286             success &= false;
287         }
288 
289         return success;
290     }
291 
292     /**
293      * This checks to see if the user is valid and active for this module
294      * 
295      * @param document
296      * @return false if this user is not valid or active for being a delegate
297      */
298     protected boolean checkDelegateUserRules(MaintenanceDocument document) {
299         boolean success = true;
300         final Person accountDelegate = newDelegate.getAccountDelegate();
301 
302         // if the user doesn't exist, then do nothing, it'll fail the existence test elsewhere
303         if (ObjectUtils.isNull(accountDelegate)) {
304             return success;
305         }
306         
307         // if the document is inactivating an account delegate, don't bother validating the user
308         if (document.getOldMaintainableObject() != null && document.getOldMaintainableObject().getBusinessObject() != null && ((AccountDelegate)document.getOldMaintainableObject().getBusinessObject()).isActive() && !((AccountDelegate)document.getNewMaintainableObject().getBusinessObject()).isActive()) {
309             return success;
310         }
311 
312         if (StringUtils.isBlank(accountDelegate.getEntityId()) || !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name, accountDelegate.getPrincipalId())) {
313             super.putFieldError("accountDelegate.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountDelegate.getName(), OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name});
314             success = false;
315         }
316         return success;
317     }
318 }