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