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 java.sql.Date;
019 import java.sql.Timestamp;
020 import java.util.Calendar;
021 import java.util.HashMap;
022 import java.util.HashSet;
023 import java.util.List;
024 import java.util.Map;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.apache.commons.lang.time.DateUtils;
028 import org.kuali.kfs.coa.businessobject.Account;
029 import org.kuali.kfs.coa.businessobject.AccountGlobal;
030 import org.kuali.kfs.coa.businessobject.AccountGlobalDetail;
031 import org.kuali.kfs.coa.businessobject.SubFundGroup;
032 import org.kuali.kfs.coa.service.OrganizationService;
033 import org.kuali.kfs.coa.service.SubFundGroupService;
034 import org.kuali.kfs.sys.KFSConstants;
035 import org.kuali.kfs.sys.KFSKeyConstants;
036 import org.kuali.kfs.sys.context.SpringContext;
037 import org.kuali.rice.kim.bo.Person;
038 import org.kuali.rice.kns.bo.PersistableBusinessObject;
039 import org.kuali.rice.kns.document.MaintenanceDocument;
040 import org.kuali.rice.kns.service.BusinessObjectService;
041 import org.kuali.rice.kns.service.DictionaryValidationService;
042 import org.kuali.rice.kns.util.GlobalVariables;
043 import org.kuali.rice.kns.util.ObjectUtils;
044
045 /**
046 * This class represents the business rules for the maintenance of {@link AccountGlobal} business objects
047 */
048 public class AccountGlobalRule extends GlobalDocumentRuleBase {
049 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountGlobalRule.class);
050
051 protected static final String GENERAL_FUND_CD = "GF";
052 protected static final String RESTRICTED_FUND_CD = "RF";
053 protected static final String ENDOWMENT_FUND_CD = "EN";
054 protected static final String PLANT_FUND_CD = "PF";
055
056 protected static final String RESTRICTED_CD_RESTRICTED = "R";
057 protected static final String RESTRICTED_CD_UNRESTRICTED = "U";
058 protected static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T";
059
060 protected static final String SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS = "MPRACT";
061
062 protected AccountGlobal newAccountGlobal;
063 protected Timestamp today;
064
065 public AccountGlobalRule() {
066 super();
067 }
068
069 /**
070 * This method sets the convenience objects like newAccountGlobal and oldAccount, so you have short and easy handles to the new
071 * and old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to
072 * load all sub-objects from the DB by their primary keys, if available.
073 */
074 @Override
075 public void setupConvenienceObjects() {
076
077 // setup newDelegateGlobal convenience objects, make sure all possible sub-objects are populated
078 newAccountGlobal = (AccountGlobal) super.getNewBo();
079 today = getDateTimeService().getCurrentTimestamp();
080 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components
081 }
082
083 /**
084 * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
085 * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does not fail if any of them fail (this only happens on
086 * routing)
087 *
088 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
089 */
090 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
091
092 LOG.info("processCustomSaveDocumentBusinessRules called");
093 setupConvenienceObjects();
094
095 checkEmptyValues();
096 checkGeneralRules(document);
097 checkOrganizationValidity(newAccountGlobal);
098 checkContractsAndGrants();
099 checkExpirationDate(document);
100 checkOnlyOneChartErrorWrapper(newAccountGlobal.getAccountGlobalDetails());
101 // checkFundGroup(document);
102 // checkSubFundGroup(document);
103
104 // Save always succeeds, even if there are business rule failures
105 return true;
106 }
107
108 /**
109 * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
110 * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does fail if any of these rule checks fail
111 *
112 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
113 */
114 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
115
116 LOG.info("processCustomRouteDocumentBusinessRules called");
117 setupConvenienceObjects();
118
119 // default to success
120 boolean success = true;
121
122 success &= checkEmptyValues();
123 success &= checkGeneralRules(document);
124 success &= checkContractsAndGrants();
125 success &= checkExpirationDate(document);
126 success &= checkAccountDetails(document, newAccountGlobal.getAccountGlobalDetails());
127 // success &= checkFundGroup(document);
128 // success &= checkSubFundGroup(document);
129
130 return success;
131 }
132
133 /**
134 * This method loops through the list of {@link AccountGlobalDetail}s and passes them off to checkAccountDetails for further
135 * rule analysis One rule it does check is checkOnlyOneChartErrorWrapper
136 *
137 * @param document
138 * @param details
139 * @return true if the collection of {@link AccountGlobalDetail}s passes the sub-rules
140 */
141 public boolean checkAccountDetails(MaintenanceDocument document, List<AccountGlobalDetail> details) {
142 boolean success = true;
143
144 // check if there are any accounts
145 if (details.size() == 0) {
146
147 putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS);
148
149 success = false;
150 }
151 else {
152 // check each account
153 int index = 0;
154 for (AccountGlobalDetail dtl : details) {
155 String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]";
156 GlobalVariables.getMessageMap().addToErrorPath(errorPath);
157 success &= checkAccountDetails(dtl);
158 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
159 index++;
160 }
161 success &= checkOnlyOneChartErrorWrapper(details);
162 }
163
164 return success;
165 }
166
167 /**
168 * This method ensures that each {@link AccountGlobalDetail} is valid and has a valid account number
169 *
170 * @param dtl
171 * @return true if the detail object contains a valid account
172 */
173 public boolean checkAccountDetails(AccountGlobalDetail dtl) {
174 boolean success = true;
175 int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
176 getDictionaryValidationService().validateBusinessObject(dtl);
177 if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) {
178 dtl.refreshReferenceObject("account");
179 if (ObjectUtils.isNull(dtl.getAccount())) {
180 GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[] { dtl.getChartOfAccountsCode(), dtl.getAccountNumber() });
181 }
182 }
183 success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;
184
185 return success;
186 }
187
188 /**
189 * This method checks the basic rules for empty reference key values on a continuation account and an income stream account
190 *
191 * @return true if no empty values or partially filled out reference keys
192 */
193 protected boolean checkEmptyValues() {
194
195 LOG.info("checkEmptyValues called");
196
197 boolean success = true;
198
199 // this set confirms that all fields which are grouped (ie, foreign keys of a referenc
200 // object), must either be none filled out, or all filled out.
201 success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount");
202 success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount");
203
204 return success;
205 }
206
207 /**
208 * This method checks some of the general business rules associated with this document Such as: valid user for fiscal officer,
209 * supervisor or account manager (and not the same individual) are they trying to use an expired continuation account
210 *
211 * @param maintenanceDocument
212 * @return false on rules violation
213 */
214 protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) {
215
216 LOG.info("checkGeneralRules called");
217 Person fiscalOfficer = newAccountGlobal.getAccountFiscalOfficerUser();
218 Person accountManager = newAccountGlobal.getAccountManagerUser();
219 Person accountSupervisor = newAccountGlobal.getAccountSupervisoryUser();
220
221 boolean success = true;
222
223 if (!StringUtils.isBlank(newAccountGlobal.getAccountFiscalOfficerSystemIdentifier()) && (ObjectUtils.isNull(fiscalOfficer) || StringUtils.isBlank(fiscalOfficer.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER, fiscalOfficer.getPrincipalId()))) {
224 final String fiscalOfficerName = fiscalOfficer != null ? fiscalOfficer.getName() : newAccountGlobal.getAccountFiscalOfficerSystemIdentifier();
225 super.putFieldError("accountFiscalOfficerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficerName, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER});
226 success = false;
227 }
228 if (!StringUtils.isBlank(newAccountGlobal.getAccountsSupervisorySystemsIdentifier()) && (ObjectUtils.isNull(accountSupervisor) || StringUtils.isBlank(accountSupervisor.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR, accountSupervisor.getPrincipalId()))) {
229 final String accountSupervisorName = accountSupervisor != null ? accountSupervisor.getName() : newAccountGlobal.getAccountsSupervisorySystemsIdentifier();
230 super.putFieldError("accountSupervisoryUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisorName, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR});
231 success = false;
232 }
233 if (!StringUtils.isBlank(newAccountGlobal.getAccountManagerSystemIdentifier()) && (ObjectUtils.isNull(accountManager) || StringUtils.isBlank(accountManager.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER, accountManager.getPrincipalId()))) {
234 final String accountManagerName = accountManager != null ? accountManager.getName() : newAccountGlobal.getAccountManagerSystemIdentifier();
235 super.putFieldError("accountManagerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManagerName, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER});
236 success = false;
237 }
238
239 // the supervisor cannot be the same as the fiscal officer or account manager.
240 if (isSupervisorSameAsFiscalOfficer(newAccountGlobal)) {
241 success &= false;
242 putFieldError("accountsSupervisorySystemsIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
243 }
244 if (isSupervisorSameAsManager(newAccountGlobal)) {
245 success &= false;
246 putFieldError("accountManagerSystemIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
247 }
248
249 // disallow continuation account being expired
250 if (isContinuationAccountExpired(newAccountGlobal)) {
251 success &= false;
252 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
253 }
254
255 // loop over change detail objects to test if the supervisor/FO/mgr restrictions are in place
256 // only need to do this check if the entered information does not already violate the rules
257 if (!isSupervisorSameAsFiscalOfficer(newAccountGlobal) && !isSupervisorSameAsManager(newAccountGlobal)) {
258 success &= checkAllAccountUsers(newAccountGlobal, fiscalOfficer, accountManager, accountSupervisor);
259 }
260
261 return success;
262 }
263
264 /**
265 * This method checks to make sure that if the users are filled out (fiscal officer, supervisor, manager) that they are not the
266 * same individual Only need to check this if these are new users that override existing users on the {@link Account} object
267 *
268 * @param doc
269 * @param newFiscalOfficer
270 * @param newManager
271 * @param newSupervisor
272 * @return true if the users are either not changed or pass the sub-rules
273 */
274 protected boolean checkAllAccountUsers(AccountGlobal doc, Person newFiscalOfficer, Person newManager, Person newSupervisor) {
275 boolean success = true;
276
277 if (LOG.isDebugEnabled()) {
278 LOG.debug("newSupervisor: " + newSupervisor);
279 LOG.debug("newFiscalOfficer: " + newFiscalOfficer);
280 LOG.debug("newManager: " + newManager);
281 }
282 // only need to do this check if at least one of the user fields is
283 // non null
284 if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
285 // loop over all AccountGlobalDetail records
286 int index = 0;
287 for (AccountGlobalDetail detail : doc.getAccountGlobalDetails()) {
288 success &= checkAccountUsers(detail, newFiscalOfficer, newManager, newSupervisor, index);
289 index++;
290 }
291 }
292
293 return success;
294 }
295
296 /**
297 * This method checks that the new users (fiscal officer, supervisor, manager) are not the same individual for the
298 * {@link Account} being changed (contained in the {@link AccountGlobalDetail})
299 *
300 * @param detail - where the Account information is stored
301 * @param newFiscalOfficer
302 * @param newManager
303 * @param newSupervisor
304 * @param index - for storing the error line
305 * @return true if the new users pass this sub-rule
306 */
307 protected boolean checkAccountUsers(AccountGlobalDetail detail, Person newFiscalOfficer, Person newManager, Person newSupervisor, int index) {
308 boolean success = true;
309
310 // only need to do this check if at least one of the user fields is non null
311 if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
312 // loop over all AccountGlobalDetail records
313 detail.refreshReferenceObject("account");
314 Account account = detail.getAccount();
315 if (ObjectUtils.isNotNull(account)){
316 if (LOG.isDebugEnabled()) {
317 LOG.debug("old-Supervisor: " + account.getAccountSupervisoryUser());
318 LOG.debug("old-FiscalOfficer: " + account.getAccountFiscalOfficerUser());
319 LOG.debug("old-Manager: " + account.getAccountManagerUser());
320 }
321 // only need to check if they are not being overridden by the change document
322 if (newSupervisor != null && newSupervisor.getPrincipalId() != null) {
323 if (areTwoUsersTheSame(newSupervisor, account.getAccountFiscalOfficerUser())) {
324 success = false;
325 putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_FISCAL_OFFICER, new String[] { account.getAccountFiscalOfficerUser().getPrincipalName(), "Fiscal Officer", detail.getAccountNumber() });
326 }
327 if (areTwoUsersTheSame(newSupervisor, account.getAccountManagerUser())) {
328 success = false;
329 putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_ACCT_MGR, new String[] { account.getAccountManagerUser().getPrincipalName(), "Account Manager", detail.getAccountNumber() });
330 }
331 }
332 if (newManager != null && newManager.getPrincipalId() != null) {
333 if (areTwoUsersTheSame(newManager, account.getAccountSupervisoryUser())) {
334 success = false;
335 putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_MGR_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
336 }
337 }
338 if (newFiscalOfficer != null && newFiscalOfficer.getPrincipalId() != null) {
339 if (areTwoUsersTheSame(newFiscalOfficer, account.getAccountSupervisoryUser())) {
340 success = false;
341 putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_FISCAL_OFFICER_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
342 }
343 }
344 }
345 else {
346 LOG.warn("AccountGlobalDetail object has null account object:" + detail.getChartOfAccountsCode() + "-" + detail.getAccountNumber());
347 }
348 }
349
350 return success;
351 }
352
353 /**
354 * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
355 * {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
356 *
357 * @param accountGlobals
358 * @return true if the two users are the same
359 */
360 protected boolean isSupervisorSameAsFiscalOfficer(AccountGlobal accountGlobals) {
361 return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountFiscalOfficerUser());
362 }
363
364 /**
365 * This method is a helper method for checking if the supervisor user is the same as the manager Calls
366 * {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
367 *
368 * @param accountGlobals
369 * @return true if the two users are the same
370 */
371 protected boolean isSupervisorSameAsManager(AccountGlobal accountGlobals) {
372 return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountManagerUser());
373 }
374
375 /**
376 * This method checks to see if two users are the same Person using their identifiers
377 *
378 * @param user1
379 * @param user2
380 * @return true if these two users are the same
381 */
382 protected boolean areTwoUsersTheSame(Person user1, Person user2) {
383 if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) {
384 return false;
385 }
386 if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) {
387 return false;
388 }
389 return user1.getPrincipalId().equals(user2.getPrincipalId());
390 }
391
392 /**
393 * This method checks to see if any expiration date field rules were violated Loops through each detail object and calls
394 * {@link AccountGlobalRule#checkExpirationDate(MaintenanceDocument, AccountGlobalDetail)}
395 *
396 * @param maintenanceDocument
397 * @return false on rules violation
398 */
399 protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) {
400 LOG.info("checkExpirationDate called");
401
402 boolean success = true;
403 Date newExpDate = newAccountGlobal.getAccountExpirationDate();
404
405 // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
406 // the acct_expiration_dt must be changed to a date that is today or later
407 if (ObjectUtils.isNotNull(newExpDate)) {
408 if (ObjectUtils.isNotNull(newAccountGlobal.getSubFundGroup())) {
409 if (!SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newAccountGlobal.getSubFundGroup())) {
410 if (!newExpDate.after(today) && !newExpDate.equals(today)) {
411 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
412 success &= false;
413 }
414 }
415 }
416 }
417
418 // a continuation account is required if the expiration date is completed.
419 success &= checkContinuationAccount(maintenanceDocument, newExpDate);
420
421 for (AccountGlobalDetail detail : newAccountGlobal.getAccountGlobalDetails()) {
422 success &= checkExpirationDate(maintenanceDocument, detail);
423 }
424 return success;
425 }
426
427 /**
428 * This method checks to see if any expiration date field rules were violated in relation to the given detail record
429 *
430 * @param maintenanceDocument
431 * @param detail - the account detail we are investigating
432 * @return false on rules violation
433 */
434 protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument, AccountGlobalDetail detail) {
435 boolean success = true;
436 Date newExpDate = newAccountGlobal.getAccountExpirationDate();
437
438 // load the object by keys
439 Account account = (Account) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Account.class, detail.getPrimaryKeys());
440 if (ObjectUtils.isNotNull(account)) {
441 Date oldExpDate = account.getAccountExpirationDate();
442
443 // When updating an account expiration date, the date must be today or later
444 // (except for C&G accounts). Only run this test if this maint doc
445 // is an edit doc
446 if (isUpdatedExpirationDateInvalid(account, newAccountGlobal)) {
447 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
448 success &= false;
449 }
450
451 // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
452 // the acct_expiration_dt must be changed to a date that is today or later
453 if (ObjectUtils.isNotNull(newExpDate) && ObjectUtils.isNull(newAccountGlobal.getSubFundGroup())) {
454 if (ObjectUtils.isNotNull(account.getSubFundGroup())) {
455 if (!account.isForContractsAndGrants()) {
456 if (!newExpDate.after(today) && !newExpDate.equals(today)) {
457 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
458 success &= false;
459 }
460 }
461 }
462 }
463 // acct_expiration_dt can not be before acct_effect_dt
464 Date effectiveDate = account.getAccountEffectiveDate();
465 if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) {
466 if (newExpDate.before(effectiveDate)) {
467 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
468 success &= false;
469 }
470 }
471 }
472
473 return success;
474 }
475
476 /*
477 * protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { // get today's date, with no time
478 * component Timestamp todaysDate = getDateTimeService().getCurrentTimestamp();
479 * todaysDate.setTime(DateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); // TODO: convert this to using Wes'
480 * kuali DateUtils once we're using Date's instead of Timestamp // get the expiration date, if any Timestamp expirationDate =
481 * newAccount.getAccountExpirationDate(); if (ObjectUtils.isNull(expirationDate)) { putFieldError("accountExpirationDate",
482 * KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } // when closing an account,
483 * the account expiration date must be the current date or earlier expirationDate.setTime(DateUtils.truncate(expirationDate,
484 * Calendar.DAY_OF_MONTH).getTime()); if (expirationDate.after(todaysDate)) { putFieldError("accountExpirationDate",
485 * KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } return true; }
486 */
487
488 /**
489 * This method checks to see if the updated expiration is not a valid one Only gets checked for specific {@link SubFundGroup}s
490 *
491 * @param oldAccount
492 * @param newAccountGlobal
493 * @return true if date has changed and is invalid
494 */
495 protected boolean isUpdatedExpirationDateInvalid(Account oldAccount, AccountGlobal newAccountGlobal) {
496
497 Date oldExpDate = oldAccount.getAccountExpirationDate();
498 Date newExpDate = newAccountGlobal.getAccountExpirationDate();
499
500 // When updating an account expiration date, the date must be today or later
501 // (except for C&G accounts). Only run this test if this maint doc
502 // is an edit doc
503 boolean expDateHasChanged = false;
504
505 // if the old version of the account had no expiration date, and the new
506 // one has a date
507 if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
508 expDateHasChanged = true;
509 }
510
511 // if there was an old and a new expDate, but they're different
512 else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
513 if (!oldExpDate.equals(newExpDate)) {
514 expDateHasChanged = true;
515 }
516 }
517
518 // if the expiration date hasnt changed, we're not interested
519 if (!expDateHasChanged) {
520 return false;
521 }
522
523 // if a subFundGroup isnt present, we cannot continue the testing
524 SubFundGroup subFundGroup = newAccountGlobal.getSubFundGroup();
525 if (ObjectUtils.isNull(subFundGroup)) {
526 return false;
527 }
528
529 // get the fundGroup code
530 String fundGroupCode = newAccountGlobal.getSubFundGroup().getFundGroupCode().trim();
531
532 // if the account is part of the CG fund group, then this rule does not
533 // apply, so we're done
534 if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newAccountGlobal.getSubFundGroup())) {
535 return false;
536 }
537
538 // at this point, we know its not a CG fund group, so we must apply the rule
539
540 // expirationDate must be today or later than today (cannot be before today)
541 if (newExpDate.equals(today) || newExpDate.after(today)) {
542 return false;
543 }
544 else
545 return true;
546 }
547
548
549 /**
550 * This method tests whether the continuation account entered (if any) has expired or not.
551 *
552 * @param accountGlobals
553 * @return true if the continuation account has expired
554 */
555 protected boolean isContinuationAccountExpired(AccountGlobal accountGlobals) {
556
557 boolean result = false;
558
559 String chartCode = accountGlobals.getContinuationFinChrtOfAcctCd();
560 String accountNumber = accountGlobals.getContinuationAccountNumber();
561
562 // if either chartCode or accountNumber is not entered, then we
563 // cant continue, so exit
564 if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) {
565 return result;
566 }
567
568 // attempt to retrieve the continuation account from the DB
569 Account continuation = null;
570 Map<String,String> pkMap = new HashMap<String,String>();
571 pkMap.put("chartOfAccountsCode", chartCode);
572 pkMap.put("accountNumber", accountNumber);
573 continuation = (Account) super.getBoService().findByPrimaryKey(Account.class, pkMap);
574
575 // if the object doesnt exist, then we cant continue, so exit
576 if (ObjectUtils.isNull(continuation)) {
577 return result;
578 }
579
580 // at this point, we have a valid continuation account, so we just need to
581 // know whether its expired or not
582 result = continuation.isExpired();
583
584 return result;
585 }
586
587 /**
588 * This method checks to see if any Contracts and Grants business rules were violated
589 *
590 * @return false on rules violation
591 */
592 protected boolean checkContractsAndGrants() {
593
594 LOG.info("checkContractsAndGrants called");
595
596 boolean success = true;
597
598 // Income Stream account is required if this account is CG fund group,
599 // or GF (general fund) fund group (with some exceptions)
600 success &= checkCgIncomeStreamRequired(newAccountGlobal);
601
602 return success;
603 }
604
605 /**
606 * This method checks to see if the contracts and grants income stream account is required
607 *
608 * @param accountGlobals
609 * @return false if it is required (and not entered) or invalid/inactive
610 */
611 protected boolean checkCgIncomeStreamRequired(AccountGlobal accountGlobals) {
612
613 boolean result = true;
614 boolean required = false;
615
616 // if the subFundGroup object is null, we cant test, so exit
617 if (ObjectUtils.isNull(accountGlobals.getSubFundGroup())) {
618 return result;
619 }
620
621 // retrieve the subfundcode and fundgroupcode
622 String subFundGroupCode = accountGlobals.getSubFundGroupCode().trim();
623 String fundGroupCode = accountGlobals.getSubFundGroup().getFundGroupCode().trim();
624
625 // if this is a CG fund group, then its required
626 if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(accountGlobals.getSubFundGroup())) {
627 required = true;
628 }
629
630 // if this is a general fund group, then its required
631 else if (GENERAL_FUND_CD.equalsIgnoreCase(fundGroupCode)) {
632 // unless its part of the MPRACT subfundgroup
633 if (!SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS.equalsIgnoreCase(subFundGroupCode)) {
634 required = true;
635 }
636 }
637
638 // if the income stream account is not required, then we're done
639 if (!required) {
640 return result;
641 }
642
643 // make sure both coaCode and accountNumber are filled out
644 result &= checkEmptyBOField("incomeStreamAccountNumber", accountGlobals.getIncomeStreamAccountNumber(), "When Fund Group is CG or GF, Income Stream Account Number");
645 result &= checkEmptyBOField("incomeStreamFinancialCoaCode", accountGlobals.getIncomeStreamFinancialCoaCode(), "When Fund Group is CG or GF, Income Stream Chart Of Accounts Code");
646
647 // if both fields arent present, then we're done
648 if (result == false) {
649 return result;
650 }
651
652 // do an existence/active test
653 DictionaryValidationService dvService = super.getDictionaryValidationService();
654 boolean referenceExists = dvService.validateReferenceExists(accountGlobals, "incomeStreamAccount");
655 if (!referenceExists) {
656 putFieldError("incomeStreamAccount", KFSKeyConstants.ERROR_EXISTENCE, "Income Stream Account: " + accountGlobals.getIncomeStreamFinancialCoaCode() + "-" + accountGlobals.getIncomeStreamAccountNumber());
657 result &= false;
658 }
659
660 return result;
661 }
662
663 /**
664 * This method calls checkAccountDetails checkExpirationDate checkOnlyOneChartAddLineErrorWrapper whenever a new
665 * {@link AccountGlobalDetail} is added to this global
666 *
667 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
668 * java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject)
669 */
670 public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
671 AccountGlobalDetail detail = (AccountGlobalDetail) bo;
672 boolean success = true;
673
674 success &= checkAccountDetails(detail);
675 success &= checkExpirationDate(document, detail);
676 success &= checkOnlyOneChartAddLineErrorWrapper(detail, newAccountGlobal.getAccountGlobalDetails());
677
678 return success;
679 }
680
681 /**
682 * This method validates that a continuation account is required and that the values provided exist
683 *
684 * @param document An instance of the maintenance document being validated.
685 * @param newExpDate The expiration date assigned to the account being validated for submission.
686 * @return True if the continuation account values are valid for the associated account, false otherwise.
687 */
688 protected boolean checkContinuationAccount(MaintenanceDocument document, Date newExpDate) {
689 LOG.info("checkContinuationAccount called");
690
691 boolean result = true;
692 boolean continuationAccountIsValid = true;
693
694 // make sure both coaCode and accountNumber are filled out
695 if (ObjectUtils.isNotNull(newExpDate)) {
696 if (!checkEmptyValue(newAccountGlobal.getContinuationAccountNumber())) {
697 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
698 continuationAccountIsValid = false;
699 }
700 if (!checkEmptyValue(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
701 putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
702 continuationAccountIsValid = false;
703 }
704 }
705
706 // if both fields aren't present, then we're done
707 if (continuationAccountIsValid && ObjectUtils.isNotNull(newAccountGlobal.getContinuationAccountNumber()) && ObjectUtils.isNotNull(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
708 // do an existence/active test
709 DictionaryValidationService dvService = super.getDictionaryValidationService();
710 boolean referenceExists = dvService.validateReferenceExists(newAccountGlobal, "continuationAccount");
711 if (!referenceExists) {
712 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, "Continuation Account: " + newAccountGlobal.getContinuationFinChrtOfAcctCd() + "-" + newAccountGlobal.getContinuationAccountNumber());
713 continuationAccountIsValid = false;
714 }
715 }
716
717 if (continuationAccountIsValid) {
718 result = true;
719 }
720 else {
721 List<AccountGlobalDetail> gAcctDetails = newAccountGlobal.getAccountGlobalDetails();
722 for (AccountGlobalDetail detail : gAcctDetails) {
723 if (null != detail.getAccountNumber() && null != newAccountGlobal.getContinuationAccountNumber()) {
724 result &= detail.getAccountNumber().equals(newAccountGlobal.getContinuationAccountNumber());
725 result &= detail.getChartOfAccountsCode().equals(newAccountGlobal.getContinuationFinChrtOfAcctCd());
726 }
727 }
728 }
729
730 return result;
731 }
732
733 /**
734 * Validate that the object code on the form (if entered) is valid for all charts used in the detail sections.
735 *
736 * @param acctGlobal
737 * @return
738 */
739 protected boolean checkOrganizationValidity( AccountGlobal acctGlobal ) {
740 boolean result = true;
741
742 // check that an org has been entered
743 if ( StringUtils.isNotBlank( acctGlobal.getOrganizationCode() ) ) {
744 // get all distinct charts
745 HashSet<String> charts = new HashSet<String>(10);
746 for ( AccountGlobalDetail acct : acctGlobal.getAccountGlobalDetails() ) {
747 charts.add( acct.getChartOfAccountsCode() );
748 }
749 OrganizationService orgService = SpringContext.getBean(OrganizationService.class);
750 // test for an invalid organization
751 for ( String chartCode : charts ) {
752 if ( StringUtils.isNotBlank(chartCode) ) {
753 if ( null == orgService.getByPrimaryIdWithCaching( chartCode, acctGlobal.getOrganizationCode() ) ) {
754 result = false;
755 putFieldError("organizationCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ORG, new String[] { chartCode, acctGlobal.getOrganizationCode() } );
756 break;
757 }
758 }
759 }
760 }
761
762 return result;
763 }
764 }
765