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