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