View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.coa.document.validation.impl;
20  
21  import java.sql.Timestamp;
22  import java.util.Calendar;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.commons.lang.time.DateUtils;
26  import org.kuali.kfs.coa.businessobject.AccountDelegateModel;
27  import org.kuali.kfs.coa.businessobject.AccountDelegateModelDetail;
28  import org.kuali.kfs.coa.businessobject.Chart;
29  import org.kuali.kfs.coa.businessobject.Organization;
30  import org.kuali.kfs.sys.KFSConstants;
31  import org.kuali.kfs.sys.KFSKeyConstants;
32  import org.kuali.kfs.sys.context.SpringContext;
33  import org.kuali.kfs.sys.document.service.FinancialSystemDocumentTypeService;
34  import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;
35  import org.kuali.rice.core.api.util.type.KualiDecimal;
36  import org.kuali.rice.kns.document.MaintenanceDocument;
37  import org.kuali.rice.krad.bo.PersistableBusinessObject;
38  import org.kuali.rice.krad.util.GlobalVariables;
39  import org.kuali.rice.krad.util.ObjectUtils;
40  
41  /**
42   * This class implements the business rules specific to the {@link OrganizationRoutingModelName} Maintenance Document.
43   */
44  public class AccountDelegateModelRule extends KfsMaintenanceDocumentRuleBase {
45  
46      protected AccountDelegateModel model;
47  
48      /**
49       * This method sets the convenience objects like model, so you have short and easy handles to the new and
50       * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
51       * all sub-objects from the DB by their primary keys, if available.
52       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupConvenienceObjects()
53       */
54      @Override
55      public void setupConvenienceObjects() {
56          model = (AccountDelegateModel) super.getNewBo();
57          for (AccountDelegateModelDetail delegateModel : model.getAccountDelegateModelDetails()) {
58              delegateModel.refreshNonUpdateableReferences();
59          }
60      }
61  
62      /**
63       * This performs rules checks on document approve
64       * <ul>
65       * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li>
66       * </ul>
67       * This rule fails on business rule failures
68       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
69       */
70      @Override
71      protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
72          setupConvenienceObjects();
73          return checkSimpleRules(document, this.model);
74      }
75  
76      /**
77       * This performs rules checks on document route
78       * <ul>
79       * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li>
80       * </ul>
81       * This rule fails on business rule failures
82       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
83       */
84      @Override
85      protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
86          setupConvenienceObjects();
87          return checkSimpleRules(document, this.model);
88      }
89  
90      /**
91       * This performs rules checks on document save
92       * <ul>
93       * <li>{@link AccountDelegateModelRule#checkSimpleRules(OrganizationRoutingModelName)}</li>
94       * </ul>
95       * This rule does not fail on business rule failures
96       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
97       */
98      @Override
99      protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
100         setupConvenienceObjects();
101         checkSimpleRules(document, this.model);
102         return true;
103     }
104 
105     /**
106      * This method calls
107      * <ul>
108      * <li>{@link AccountDelegateModelRule#checkSimpleRulesForOrganizationRoutingModel(OrganizationRoutingModelName, OrganizationRoutingModel)}</li>
109      * </ul>
110      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
111      *      java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
112      */
113     @Override
114     public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {
115         setupConvenienceObjects();
116         final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
117         return checkSimpleRulesForOrganizationRoutingModel(document, this.model, (AccountDelegateModelDetail) line, documentService);
118     }
119 
120     /**
121      * Checks the given rules against the entire Organization Routing Model parent.
122      *
123      * @param globalDelegateTemplate the Organization Routing Model parent to check
124      * @return true if document passes all rules, false if otherwise
125      */
126     protected boolean checkSimpleRules(MaintenanceDocument document, AccountDelegateModel globalDelegateTemplate) {
127         boolean success = true;
128 
129         success &= checkModelNameHasAtLeastOneModel(globalDelegateTemplate);
130 
131         int line = 0;
132         final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
133         for (AccountDelegateModelDetail delegateModel : globalDelegateTemplate.getAccountDelegateModelDetails()) {
134             GlobalVariables.getMessageMap().addToErrorPath(MAINTAINABLE_ERROR_PATH + ".accountDelegateModelDetails[" + line + "].");
135             success &= checkSimpleRulesForOrganizationRoutingModel(document, globalDelegateTemplate, delegateModel, documentService);
136             GlobalVariables.getMessageMap().addToErrorPath(MAINTAINABLE_ERROR_PATH + ".accountDelegateModelDetails[" + line + "].");
137             line++;
138         }
139 
140         return success;
141     }
142 
143      /**
144      * This method checks a series of basic rules for a single org routing model.
145      *
146      * @return true if model passes all the checks, false if otherwise
147      */
148     protected boolean checkSimpleRulesForOrganizationRoutingModel(MaintenanceDocument document, AccountDelegateModel globalDelegateTemplate, AccountDelegateModelDetail delegateModel, FinancialSystemDocumentTypeService documentService) {
149         boolean success = true;
150 
151         if (delegateModel.isActive()) {
152             success &= checkDelegateFromAmountPositive(delegateModel);
153             success &= checkDelegateToAmountGreaterThanFromAmount(delegateModel);
154             success &= checkDelegateUserRules(document, delegateModel);
155             success &= checkPrimaryRoutePerDocType(globalDelegateTemplate, delegateModel);
156             success &= checkDelegateDocumentTypeCode(delegateModel.getFinancialDocumentTypeCode(), documentService);
157         }
158 
159         return success;
160     }
161 
162     /**
163      * This method makes certain that the collection of account delegates in the "mo itdel" has at least one account delegate
164      * template in it.
165      *
166      * @param globalDelegateTemplate the account delegate model to check
167      * @return true if account delegate model has at least one account delegate template in it
168      */
169     protected boolean checkModelNameHasAtLeastOneModel(AccountDelegateModel globalDelegateTemplate) {
170         boolean success = true;
171         if (globalDelegateTemplate.getAccountDelegateModelDetails().size() == 0) {
172             success = false;
173             GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "add.accountDelegateModelDetails.financialDocumentTypeCode", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_DELEGATE, new String[0]);
174         }
175         
176         success &= checkDelegateModel(globalDelegateTemplate);        
177         return success;
178     }
179 
180     private boolean checkDelegateModel(AccountDelegateModel globalDelegateTemplate) {
181         boolean success = true;
182         Chart accounts = globalDelegateTemplate.getChartOfAccounts();
183         if (ObjectUtils.isNull(accounts)) {
184             GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_CHART_CODE);
185             success = false;
186         }
187         Organization organization = globalDelegateTemplate.getOrganization();
188         if (ObjectUtils.isNull(organization)) {
189             GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "organizationCode", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_ORGANIZATION_CODE);
190             success = false;
191         }
192 
193         return success;
194     }
195 
196     /**
197      * This method checks that the account delegate model has at least one active "model" within it.
198      *
199      * @param globalDelegateTemplate the account delegate model to check
200      * @return true if account delegate model has at least one active model in it.
201      */
202     // method not currently in use, as per Bill's comments in KULRNE-4805
203     protected boolean checkModelNameHasAtLeastOneActiveModel(AccountDelegateModel globalDelegateTemplate) {
204         boolean success = true;
205         int activeModelCount = 0;
206 
207         for (AccountDelegateModelDetail mdl : globalDelegateTemplate.getAccountDelegateModelDetails()) {
208             if (mdl.isActive()) {
209                 activeModelCount++;
210             }
211         }
212 
213         if (activeModelCount == 0) {
214             success = false;
215             if (globalDelegateTemplate.getAccountDelegateModelDetails().size() == 0) {
216                 GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "add.accountDelegateModelDetails.active", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_ACTIVE_DELEGATE, new String[0]);
217             }
218             else {
219                 GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + "accountDelegateModelDetails[0].active", KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_ACTIVE_DELEGATE, new String[0]);
220             }
221         }
222         return success;
223     }
224 
225     /**
226      * Checks that if approval from amount is not null, then it is positive
227      *
228      * @param delegateModel Organization Routing Model to check
229      * @return true if Organization Routing Model passes the checks, false if otherwise
230      */
231     protected boolean checkDelegateFromAmountPositive(AccountDelegateModelDetail delegateModel) {
232         boolean result = true;
233         if (!ObjectUtils.isNull(delegateModel.getApprovalFromThisAmount())) {
234             if (delegateModel.getApprovalFromThisAmount().isLessThan(KualiDecimal.ZERO)) {
235                 GlobalVariables.getMessageMap().putError("approvalFromThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE, new String[0]);
236                 result = false;
237             }
238         }
239         return result;
240     }
241 
242     /**
243      * Checks that if approval from amount is null, that approval to this amount is null or zero; and then checks that approval to
244      * amount is greater than or equal to approval from amount.
245      *
246      * @param delegateModel Organization Routing Model to check
247      * @return true if the Organization Routing Model passes the checks, false if otherwise
248      */
249     protected boolean checkDelegateToAmountGreaterThanFromAmount(AccountDelegateModelDetail delegateModel) {
250         boolean result = true;
251         if (!ObjectUtils.isNull(delegateModel.getApprovalFromThisAmount())) {
252             if (!ObjectUtils.isNull(delegateModel.getApprovalToThisAmount())) {
253                 if (delegateModel.getApprovalToThisAmount().isLessThan(delegateModel.getApprovalFromThisAmount())) {
254                     GlobalVariables.getMessageMap().putError("approvalToThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO, new String[0]);
255                     result = false;
256                 }
257             }
258         }
259         if (!ObjectUtils.isNull(delegateModel.getApprovalToThisAmount()) && delegateModel.getApprovalToThisAmount().isLessThan(KualiDecimal.ZERO)) {
260             GlobalVariables.getMessageMap().putError("approvalToThisAmount", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO, new String[0]);
261             result = false;
262         }
263         return result;
264     }
265 
266     /**
267      * Checks that the account delegate listed exists in the system, and that user has an active status and is a professional type
268      *
269      * @param delegateModel the Organization Routing Model to check
270      * @return true if delegate user passes the rules described above; false if they fail
271      */
272     protected boolean checkDelegateUserRules(MaintenanceDocument document, AccountDelegateModelDetail delegateModel) {
273         boolean success = true;
274 
275         // refresh account delegate
276         try {
277             delegateModel.setAccountDelegate(SpringContext.getBean(org.kuali.rice.kim.api.identity.PersonService.class).getPerson(delegateModel.getAccountDelegateUniversalId()));
278         }
279         catch (Exception e) {
280             if (LOG.isDebugEnabled()) {
281                 LOG.debug("User Not Found Exception: " + e);
282             }
283         }
284 
285         // user must exist
286         if ((delegateModel.getAccountDelegate() == null) || (delegateModel.getAccountDelegate().getPrincipalId() == null)){
287             GlobalVariables.getMessageMap().putError("accountDelegate.principalName", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_DOESNT_EXIST, new String[0]);
288             success = false;
289         }
290 
291         if (success) {
292             if (!getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name, delegateModel.getAccountDelegate().getPrincipalId())) {
293                 super.putFieldError("accountDelegate.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {delegateModel.getAccountDelegate().getName(), KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name});
294                 success = false;
295             }
296         }
297 
298         return success;
299     }
300 
301     /**
302      * This method validates the rule that says there can be only one PrimaryRoute delegate for each given docType. It checks the
303      * delegateGlobalToTest against the list, to determine whether adding this new delegateGlobalToTest would violate any
304      * PrimaryRoute business rule violations. If any of the incoming variables is null or empty, the method will do nothing, and
305      * return Null. It will only process the business rules if there is sufficient data to do so.
306      *
307      * @param delegateGlobalToTest A delegateGlobal line that you want to test against the list.
308      * @param delegateGlobals A List of delegateGlobal items that is being tested against.
309      * @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
310      * otherwise it checks to make sure that there is indeed one model marked as the primary route
311      */
312     protected boolean checkPrimaryRoutePerDocType(AccountDelegateModel globalDelegateTemplate, AccountDelegateModelDetail delegateModel) {
313         boolean success = true;
314 
315         // exit immediately if the adding line isnt a Primary routing
316         if (delegateModel == null || globalDelegateTemplate == null || globalDelegateTemplate.getAccountDelegateModelDetails().isEmpty()) {
317             return success;
318         }
319         if (!delegateModel.getAccountDelegatePrimaryRoutingIndicator()) {
320             return success;
321         }
322         if (StringUtils.isBlank(delegateModel.getFinancialDocumentTypeCode())) {
323             return success;
324         }
325 
326         // at this point, the delegateGlobal being added is a Primary for ALL docTypes, so we need to
327         // test whether any in the existing list are also Primary, regardless of docType
328         String docType = delegateModel.getFinancialDocumentTypeCode();
329         for (AccountDelegateModelDetail currDelegateModel : globalDelegateTemplate.getAccountDelegateModelDetails()) {
330             if (currDelegateModel.isActive() && !delegateModel.equals(currDelegateModel) && currDelegateModel.getAccountDelegatePrimaryRoutingIndicator() && delegateModel.getFinancialDocumentTypeCode().equals(currDelegateModel.getFinancialDocumentTypeCode())) {
331                 success = false;
332                 GlobalVariables.getMessageMap().putError("accountDelegatePrimaryRoutingIndicator", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_DELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE, new String[0]);
333             }
334         }
335 
336         return success;
337     }
338 
339     /**
340      * Validates the document type code for the delegate, to make sure it is a Financial System document type code
341      * @param documentTypeCode the document type code to check
342      * @param delegateService a helpful instance of the delegate service, so new ones don't have to be created all the time
343      * @return true if the document type code is valid, false otherwise
344      */
345     protected boolean checkDelegateDocumentTypeCode(String documentTypeCode, FinancialSystemDocumentTypeService documentService) {
346         if (!documentService.isFinancialSystemDocumentType(documentTypeCode)) {
347             putFieldError("financialDocumentTypeCode", KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { documentTypeCode, KFSConstants.ROOT_DOCUMENT_TYPE });
348             return false;
349         }
350         return true;
351     }
352 
353 }
354