001: /*
002: * Copyright 2006-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.chart.rules;
017:
018: import java.util.Collection;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import org.apache.commons.lang.StringUtils;
024: import org.kuali.core.bo.user.UniversalUser;
025: import org.kuali.core.document.MaintenanceDocument;
026: import org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase;
027: import org.kuali.core.service.DataDictionaryService;
028: import org.kuali.core.util.GlobalVariables;
029: import org.kuali.core.util.ObjectUtils;
030: import org.kuali.kfs.KFSConstants;
031: import org.kuali.kfs.KFSKeyConstants;
032: import org.kuali.kfs.KFSPropertyConstants;
033: import org.kuali.kfs.context.SpringContext;
034: import org.kuali.kfs.service.ParameterEvaluator;
035: import org.kuali.kfs.service.ParameterService;
036: import org.kuali.module.chart.bo.A21SubAccount;
037: import org.kuali.module.chart.bo.IcrAutomatedEntry;
038: import org.kuali.module.chart.bo.SubAccount;
039: import org.kuali.module.chart.service.SubFundGroupService;
040: import org.kuali.module.financial.service.UniversityDateService;
041:
042: /**
043: * This class implements the business rules specific to the {@link SubAccount} Maintenance Document.
044: */
045: public class SubAccountRule extends MaintenanceDocumentRuleBase {
046:
047: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
048: .getLogger(SubAccountRule.class);
049:
050: public static final String CG_A21_TYPE_COST_SHARING = "CS";
051: public static final String CG_A21_TYPE_ICR = "EX";
052:
053: private SubAccount oldSubAccount;
054: private SubAccount newSubAccount;
055: private boolean cgAuthorized;
056:
057: /**
058: * Constructs a SubAccountRule and pseudo-inject some services
059: */
060: public SubAccountRule() {
061: super ();
062: setCgAuthorized(false);
063: }
064:
065: /**
066: * This performs rules checks on document approve
067: * <ul>
068: * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
069: * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
070: * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
071: * </ul>
072: * This rule fails on business rule failures
073: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
074: */
075: protected boolean processCustomApproveDocumentBusinessRules(
076: MaintenanceDocument document) {
077:
078: LOG
079: .info("Entering processCustomApproveDocumentBusinessRules()");
080:
081: // set whether the user is authorized to modify the CG fields
082: setCgAuthorized(isCgAuthorized(GlobalVariables.getUserSession()
083: .getUniversalUser()));
084:
085: // check that all sub-objects whose keys are specified have matching objects in the db
086: checkForPartiallyEnteredReportingFields();
087:
088: // process CG rules if appropriate
089: checkCgRules(document);
090:
091: return true;
092: }
093:
094: /**
095: * This performs rules checks on document route
096: * <ul>
097: * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
098: * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
099: * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
100: * </ul>
101: * This rule fails on business rule failures
102: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
103: */
104: protected boolean processCustomRouteDocumentBusinessRules(
105: MaintenanceDocument document) {
106:
107: boolean success = true;
108:
109: LOG.info("Entering processCustomRouteDocumentBusinessRules()");
110:
111: // set whether the user is authorized to modify the CG fields
112: setCgAuthorized(isCgAuthorized(GlobalVariables.getUserSession()
113: .getUniversalUser()));
114:
115: // check that all sub-objects whose keys are specified have matching objects in the db
116: success &= checkForPartiallyEnteredReportingFields();
117:
118: // process CG rules if appropriate
119: success &= checkCgRules(document);
120:
121: return success;
122: }
123:
124: /**
125: * This performs rules checks on document save
126: * <ul>
127: * <li>{@link SubAccountRule#setCgAuthorized(boolean)}</li>
128: * <li>{@link SubAccountRule#checkForPartiallyEnteredReportingFields()}</li>
129: * <li>{@link SubAccountRule#checkCgRules(MaintenanceDocument)}</li>
130: * </ul>
131: * This rule does not fail on business rule failures
132: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
133: */
134: protected boolean processCustomSaveDocumentBusinessRules(
135: MaintenanceDocument document) {
136:
137: boolean success = true;
138:
139: LOG.info("Entering processCustomSaveDocumentBusinessRules()");
140:
141: // set whether the user is authorized to modify the CG fields
142: setCgAuthorized(isCgAuthorized(GlobalVariables.getUserSession()
143: .getUniversalUser()));
144:
145: // check that all sub-objects whose keys are specified have matching objects in the db
146: success &= checkForPartiallyEnteredReportingFields();
147:
148: // process CG rules if appropriate
149: success &= checkCgRules(document);
150:
151: return success;
152: }
153:
154: /**
155: * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
156: * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
157: * all sub-objects from the DB by their primary keys, if available.
158: *
159: * @param document - the maintenanceDocument being evaluated
160: */
161: public void setupConvenienceObjects() {
162:
163: // setup oldAccount convenience objects, make sure all possible sub-objects are populated
164: oldSubAccount = (SubAccount) super .getOldBo();
165:
166: // setup newAccount convenience objects, make sure all possible sub-objects are populated
167: newSubAccount = (SubAccount) super .getNewBo();
168: }
169:
170: /**
171: *
172: * This checks that the reporting fields are entered altogether or none at all
173: * @return false if only one reporting field filled out and not all of them, true otherwise
174: */
175: protected boolean checkForPartiallyEnteredReportingFields() {
176:
177: LOG.info("Entering checkExistenceAndActive()");
178:
179: boolean success = true;
180: boolean allReportingFieldsEntered = false;
181: boolean anyReportingFieldsEntered = false;
182:
183: // set a flag if all three reporting fields are filled (this is separated just for readability)
184: if (StringUtils.isNotEmpty(newSubAccount
185: .getFinancialReportChartCode())
186: && StringUtils.isNotEmpty(newSubAccount
187: .getFinReportOrganizationCode())
188: && StringUtils.isNotEmpty(newSubAccount
189: .getFinancialReportingCode())) {
190: allReportingFieldsEntered = true;
191: }
192:
193: // set a flag if any of the three reporting fields are filled (this is separated just for readability)
194: if (StringUtils.isNotEmpty(newSubAccount
195: .getFinancialReportChartCode())
196: || StringUtils.isNotEmpty(newSubAccount
197: .getFinReportOrganizationCode())
198: || StringUtils.isNotEmpty(newSubAccount
199: .getFinancialReportingCode())) {
200: anyReportingFieldsEntered = true;
201: }
202:
203: // if any of the three reporting code fields are filled out, all three must be, or none
204: // if any of the three are entered
205: if (anyReportingFieldsEntered && !allReportingFieldsEntered) {
206: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_RPTCODE_ALL_FIELDS_IF_ANY_FIELDS);
207: success &= false;
208: }
209:
210: return success;
211: }
212:
213: /**
214: * This method checks to see if the user is authorized for the CG fields, and if not, whether any CG fields have been entered or
215: * modified. If unauthorized changes have been made, then fail and log errors.
216: *
217: * @param document - document to test
218: * @return false if any unauthorized changes are made, true otherwise
219: */
220: protected boolean checkCgFieldsNotAuthorized(
221: MaintenanceDocument document) {
222:
223: // This method is no longer necessary, as authorization restricted
224: // fields are enforced in the general case in MaintenanceDocumentRuleBase
225: // in checkAuthorizationRestrictions().
226:
227: boolean success = true;
228:
229: // if the user is authorized, then dont bother checking any of this
230: if (getCgAuthorized()) {
231: return success;
232: }
233:
234: A21SubAccount newA21SubAccount = newSubAccount
235: .getA21SubAccount();
236:
237: // if this is an edit, then neither old nor new A21 sub account should
238: // ever be null, so we'll throw a runtime if so
239: if (ObjectUtils.isNull(newA21SubAccount)) {
240: throw new RuntimeException(
241: "A21 SubAccount object is null on a SubAccount maintenance doc. "
242: + "This should never happen.");
243: }
244:
245: // if this is a new document, disallow any values in the CG fields
246: if (document.isNew()) {
247: // success &= disallowAnyValues(newA21SubAccount.getSubAccountTypeCode(),
248: // "a21SubAccount.subAccountTypeCode");
249: success &= disallowAnyValues(newA21SubAccount
250: .getCostShareChartOfAccountCode(),
251: "a21SubAccount.costShareChartOfAccountCode");
252: success &= disallowAnyValues(newA21SubAccount
253: .getCostShareSourceAccountNumber(),
254: "a21SubAccount.costShareSourceAccountNumber");
255: success &= disallowAnyValues(newA21SubAccount
256: .getCostShareSourceSubAccountNumber(),
257: "a21SubAccount.costShareSourceSubAccountNumber");
258: success &= disallowAnyValues(newA21SubAccount
259: .getFinancialIcrSeriesIdentifier(),
260: "a21SubAccount.financialIcrSeriesIdentifier");
261: success &= disallowAnyValues(newA21SubAccount
262: .getIndirectCostRecoveryChartOfAccountsCode(),
263: "a21SubAccount.indirectCostRecoveryChartOfAccountsCode");
264: success &= disallowAnyValues(newA21SubAccount
265: .getIndirectCostRecoveryAccountNumber(),
266: "a21SubAccount.indirectCostRecoveryAccountNumber");
267: success &= disallowAnyValues(newA21SubAccount
268: .getIndirectCostRecoveryTypeCode(),
269: "a21SubAccount.indirectCostRecoveryTypeCode");
270: }
271:
272: // if this is an edit document, disallow any changes in the CG fields
273: if (document.isEdit()) {
274:
275: A21SubAccount oldA21SubAccount = oldSubAccount
276: .getA21SubAccount();
277:
278: // if this is an edit, then neither old nor new A21 sub account should
279: // ever be null, so we'll throw a runtime if so
280: if (ObjectUtils.isNull(oldA21SubAccount)) {
281: throw new RuntimeException(
282: "A21 SubAccount object is null on a SubAccount that has already been created. "
283: + "This should never happen.");
284: }
285:
286: // only try this set of tests if both old and new A21 subaccounts exist.
287: // success &= disallowChangedValues(oldA21SubAccount.getSubAccountTypeCode(),
288: // newA21SubAccount.getSubAccountTypeCode(),
289: // "a21SubAccount.subAccountTypeCode");
290: success &= disallowChangedValues(oldA21SubAccount
291: .getCostShareChartOfAccountCode(), newA21SubAccount
292: .getCostShareChartOfAccountCode(),
293: "a21SubAccount.CostShareChartOfAccountsCode");
294: success &= disallowChangedValues(oldA21SubAccount
295: .getCostShareSourceAccountNumber(),
296: newA21SubAccount.getCostShareSourceAccountNumber(),
297: "a21SubAccount.CostShareSourceAccountNumber");
298: success &= disallowChangedValues(oldA21SubAccount
299: .getCostShareSourceSubAccountNumber(),
300: newA21SubAccount
301: .getCostShareSourceSubAccountNumber(),
302: "a21SubAccount.CostShareSourceSubAccountNumber");
303: success &= disallowChangedValues(oldA21SubAccount
304: .getFinancialIcrSeriesIdentifier(),
305: newA21SubAccount.getFinancialIcrSeriesIdentifier(),
306: "a21SubAccount.financialIcrSeriesIdentifier");
307: success &= disallowChangedValues(
308: oldA21SubAccount
309: .getIndirectCostRecoveryChartOfAccountsCode(),
310: newA21SubAccount
311: .getIndirectCostRecoveryChartOfAccountsCode(),
312: "a21SubAccount.indirectCostRecoveryChartOfAccountsCode");
313: success &= disallowChangedValues(oldA21SubAccount
314: .getIndirectCostRecoveryAccountNumber(),
315: newA21SubAccount
316: .getIndirectCostRecoveryAccountNumber(),
317: "a21SubAccount.indirectCostRecoveryAccountNumber");
318: success &= disallowChangedValues(oldA21SubAccount
319: .getIndirectCostRecoveryTypeCode(),
320: newA21SubAccount.getIndirectCostRecoveryTypeCode(),
321: "a21SubAccount.indirectCostRecoveryTypeCode");
322: }
323:
324: return success;
325: }
326:
327: /**
328: *
329: * This checks to make sure that if cgAuthorized is false it succeeds immediately, otherwise it checks that all the
330: * information for CG is correctly entered and identified including:
331: * <ul>
332: * <li>If the {@link SubFundGroup} isn't for Contracts and Grants then check to make sure that the cost share and ICR fields are not empty</li>
333: * <li>If it isn't a child of CG, then the SubAccount must be of type ICR</li>
334: * </ul>
335: * @param document
336: * @return true if the user is not authorized to change CG fields, otherwise it checks the above conditions
337: */
338: protected boolean checkCgRules(MaintenanceDocument document) {
339:
340: boolean success = true;
341:
342: // short circuit if this person isnt authorized for any CG fields
343: if (!getCgAuthorized()) {
344: return success;
345: }
346:
347: // short circuit if the parent account is NOT part of a CG fund group
348: boolean a21SubAccountRefreshed = false;
349: if (ObjectUtils.isNotNull(newSubAccount.getAccount())) {
350: if (ObjectUtils.isNotNull(newSubAccount.getAccount()
351: .getSubFundGroup())) {
352:
353: // compare them, exit if the account isn't for contracts and grants
354: if (!SpringContext.getBean(SubFundGroupService.class)
355: .isForContractsAndGrants(
356: newSubAccount.getAccount()
357: .getSubFundGroup())) {
358:
359: // KULCOA-1116 - Check if CG CS and CG ICR are empty, if not throw an error
360: if (checkCgCostSharingIsEmpty() == false) {
361: putFieldError(
362: "a21SubAccount.costShareChartOfAccountCode",
363: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_CS_INVALID,
364: new String[] {
365: SpringContext
366: .getBean(
367: SubFundGroupService.class)
368: .getContractsAndGrantsDenotingAttributeLabel(),
369: SpringContext
370: .getBean(
371: SubFundGroupService.class)
372: .getContractsAndGrantsDenotingValue() });
373: }
374:
375: if (checkCgIcrIsEmpty() == false) {
376: putFieldError(
377: "a21SubAccount.indirectCostRecoveryTypeCode",
378: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_ICR_INVALID,
379: new String[] {
380: SpringContext
381: .getBean(
382: SubFundGroupService.class)
383: .getContractsAndGrantsDenotingAttributeLabel(),
384: SpringContext
385: .getBean(
386: SubFundGroupService.class)
387: .getContractsAndGrantsDenotingValue() });
388: }
389:
390: // KULRNE-4660 - this isn't the child of a CG account; sub account must be ICR type
391: if (!ObjectUtils.isNull(newSubAccount
392: .getA21SubAccount())) {
393: newSubAccount.getA21SubAccount().refresh();
394: a21SubAccountRefreshed = true;
395: if (StringUtils.isEmpty(newSubAccount
396: .getA21SubAccount()
397: .getSubAccountTypeCode())
398: || !newSubAccount
399: .getA21SubAccount()
400: .getSubAccountTypeCode()
401: .equals(
402: SubAccountRule.CG_A21_TYPE_ICR)) {
403: putFieldError(
404: "a21SubAccount.subAccountTypeCode",
405: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NON_FUNDED_ACCT_SUB_ACCT_TYPE_CODE_INVALID,
406: new String[] {
407: SpringContext
408: .getBean(
409: SubFundGroupService.class)
410: .getContractsAndGrantsDenotingAttributeLabel(),
411: SpringContext
412: .getBean(
413: SubFundGroupService.class)
414: .getContractsAndGrantsDenotingValue() });
415: }
416: }
417:
418: return success;
419: }
420: }
421: }
422:
423: // short circuit if there is no A21SubAccount object at all (ie, null)
424: if (ObjectUtils.isNull(newSubAccount.getA21SubAccount())) {
425: return success;
426: }
427:
428: // FROM HERE ON IN WE CAN ASSUME THERE IS A VALID A21 SUBACCOUNT OBJECT
429:
430: // manually refresh the a21SubAccount object, as it wont have been
431: // refreshed by the parent, as its updateable
432: // though only refresh if we didn't refresh in the checks above
433: if (!a21SubAccountRefreshed) {
434: newSubAccount.getA21SubAccount().refresh();
435: }
436:
437: ParameterEvaluator evaluator = SpringContext
438: .getBean(ParameterService.class)
439: .getParameterEvaluator(
440: SubAccount.class,
441: KFSConstants.ChartApcParms.CG_ALLOWED_SUBACCOUNT_TYPE_CODES,
442: newSubAccount.getA21SubAccount()
443: .getSubAccountTypeCode());
444: // C&G A21 Type field must be in the allowed values
445: if (!evaluator.evaluationSucceeds()) {
446: putFieldError(
447: "a21SubAccount.subAccountTypeCode",
448: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_INVALI_SUBACCOUNT_TYPE_CODES,
449: evaluator.getParameterValuesForMessage());
450: success &= false;
451: }
452:
453: // get a convenience reference to this code
454: String cgA21TypeCode = newSubAccount.getA21SubAccount()
455: .getSubAccountTypeCode();
456:
457: // if this is a Cost Sharing SubAccount, run the Cost Sharing rules
458: if (CG_A21_TYPE_COST_SHARING.trim().equalsIgnoreCase(
459: StringUtils.trim(cgA21TypeCode))) {
460: success &= checkCgCostSharingRules();
461: }
462:
463: // if this is an ICR subaccount, run the ICR rules
464: if (CG_A21_TYPE_ICR.trim().equals(
465: StringUtils.trim(cgA21TypeCode))) {
466: success &= checkCgIcrRules();
467: }
468:
469: return success;
470: }
471:
472: /**
473: *
474: * This checks that if the cost share information is filled out that it is valid and exists, or if fields are missing
475: * (such as the chart of accounts code and account number) an error is recorded
476: * @return true if all cost share fields filled out correctly, false if the chart of accounts code and account number for
477: * cost share are missing
478: */
479: protected boolean checkCgCostSharingRules() {
480:
481: boolean success = true;
482: boolean allFieldsSet = false;
483:
484: A21SubAccount a21 = newSubAccount.getA21SubAccount();
485:
486: // check to see if all required fields are set
487: if (StringUtils
488: .isNotEmpty(a21.getCostShareChartOfAccountCode())
489: && StringUtils.isNotEmpty(a21
490: .getCostShareSourceAccountNumber())) {
491: allFieldsSet = true;
492: }
493:
494: // Cost Sharing COA Code and Cost Sharing Account Number are required
495: success &= checkEmptyBOField(
496: "a21SubAccount.costShareChartOfAccountCode", a21
497: .getCostShareChartOfAccountCode(),
498: "Cost Share Chart of Accounts Code");
499: success &= checkEmptyBOField(
500: "a21SubAccount.costShareSourceAccountNumber", a21
501: .getCostShareSourceAccountNumber(),
502: "Cost Share AccountNumber");
503:
504: // existence test on Cost Share Account
505: if (allFieldsSet) {
506: if (ObjectUtils.isNull(a21.getCostShareAccount())) {
507: putFieldError(
508: "a21SubAccount.costShareSourceAccountNumber",
509: KFSKeyConstants.ERROR_EXISTENCE,
510: getDisplayName("a21SubAccount.costShareSourceAccountNumber"));
511: success &= false;
512: }
513: }
514:
515: // existence test on Cost Share SubAccount
516: if (allFieldsSet
517: && StringUtils.isNotBlank(a21
518: .getCostShareSourceSubAccountNumber())) {
519: if (ObjectUtils.isNull(a21.getCostShareSourceSubAccount())) {
520: putFieldError(
521: "a21SubAccount.costShareSourceSubAccountNumber",
522: KFSKeyConstants.ERROR_EXISTENCE,
523: getDisplayName("a21SubAccount.costShareSourceSubAccountNumber"));
524: success &= false;
525: }
526: }
527:
528: // Cost Sharing Account may not be for contracts and grants
529: if (ObjectUtils.isNotNull(a21.getCostShareAccount())) {
530: if (ObjectUtils.isNotNull(a21.getCostShareAccount()
531: .getSubFundGroup())) {
532: if (a21.getCostShareAccount().isForContractsAndGrants()) {
533: putFieldError(
534: "a21SubAccount.costShareSourceAccountNumber",
535: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_COST_SHARE_ACCOUNT_MAY_NOT_BE_CG_FUNDGROUP,
536: new String[] {
537: SpringContext
538: .getBean(
539: SubFundGroupService.class)
540: .getContractsAndGrantsDenotingAttributeLabel(),
541: SpringContext
542: .getBean(
543: SubFundGroupService.class)
544: .getContractsAndGrantsDenotingValue() });
545: success &= false;
546: }
547: }
548: }
549:
550: // The ICR fields must be empty if the sub-account type code is for cost sharing
551: if (checkCgIcrIsEmpty() == false) {
552: putFieldError(
553: "a21SubAccount.indirectCostRecoveryTypeCode",
554: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_ICR_SECTION_INVALID,
555: a21.getSubAccountTypeCode());
556: success &= false;
557: }
558:
559: return success;
560: }
561:
562: /**
563: *
564: * This checks that if the ICR information is entered that it is valid for this fiscal year and that
565: * all of its fields are valid as well (such as account)
566: * @return true if the ICR information is filled in and it is valid
567: */
568: protected boolean checkCgIcrRules() {
569:
570: boolean success = true;
571:
572: A21SubAccount a21 = newSubAccount.getA21SubAccount();
573:
574: // check required fields
575: /*
576: * success &= checkEmptyBOField("a21SubAccount.indirectCostRecoveryTypeCode", a21.getIndirectCostRecoveryTypeCode(), "ICR
577: * Type Code"); success &= checkEmptyBOField("a21SubAccount.indirectCostRecoveryChartOfAccountsCode",
578: * a21.getIndirectCostRecoveryChartOfAccountsCode(), "ICR Chart of Accounts Code"); success &=
579: * checkEmptyBOField("a21SubAccount.indirectCostRecoveryAccountNumber", a21.getIndirectCostRecoveryAccountNumber(), "ICR
580: * Account Number"); success &= checkEmptyBOField("a21SubAccount.financialIcrSeriesIdentifier",
581: * a21.getFinancialIcrSeriesIdentifier(), "Financial ICR Series ID");
582: */
583:
584: // existence check for ICR Type Code
585: if (StringUtils.isNotEmpty(a21
586: .getIndirectCostRecoveryTypeCode())) {
587: if (ObjectUtils.isNull(a21.getIcrTypeCode())) {
588: putFieldError(
589: "a21SubAccount.indirectCostRecoveryTypeCode",
590: KFSKeyConstants.ERROR_EXISTENCE,
591: "ICR Type Code: "
592: + a21.getIndirectCostRecoveryTypeCode());
593: }
594: }
595:
596: // existence check for Financial Series ID
597: if (StringUtils.isNotEmpty(a21
598: .getFinancialIcrSeriesIdentifier())) {
599:
600: Integer fiscalYear = SpringContext.getBean(
601: UniversityDateService.class).getCurrentFiscalYear();
602:
603: Map fieldValues = new HashMap();
604: fieldValues.put("financialIcrSeriesIdentifier", a21
605: .getFinancialIcrSeriesIdentifier());
606: // Collection results = boService.findMatchingOrderBy(IcrAutomatedEntry.class, fieldValues, "universityFiscalYear",
607: // true);
608: Collection results = getBoService().findMatching(
609: IcrAutomatedEntry.class, fieldValues);
610:
611: // if there are any results, we need to see if there is a match on fiscal year
612: boolean anyFound = false;
613: boolean anyFoundInThisFy = false;
614: if (!results.isEmpty()) {
615: anyFound = true;
616: for (Iterator iter = results.iterator(); iter.hasNext();) {
617: IcrAutomatedEntry icrAutomatedEntry = (IcrAutomatedEntry) iter
618: .next();
619: if (fiscalYear.equals(icrAutomatedEntry
620: .getUniversityFiscalYear())) {
621: anyFoundInThisFy = true;
622: break;
623: }
624: }
625: }
626:
627: // if there is one found, but not for this fiscal year, complain
628: // about this to the user
629: if (anyFound && !anyFoundInThisFy) {
630: putFieldError(
631: KFSPropertyConstants.A21_SUB_ACCOUNT
632: + "."
633: + KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER,
634: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_ICR_FIN_SERIES_ID_EXISTS_BUT_NOT_FOR_THIS_FY,
635: new String[] {
636: a21.getFinancialIcrSeriesIdentifier(),
637: fiscalYear.toString() });
638: }
639:
640: // if one isnt found at all, complain about that
641: if (!anyFound) {
642: String label = SpringContext
643: .getBean(DataDictionaryService.class)
644: .getAttributeLabel(
645: A21SubAccount.class,
646: KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER);
647: putFieldError(
648: KFSPropertyConstants.A21_SUB_ACCOUNT
649: + "."
650: + KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER,
651: KFSKeyConstants.ERROR_EXISTENCE, label + " ("
652: + a21.getFinancialIcrSeriesIdentifier()
653: + ")");
654: }
655: }
656:
657: // existence check for ICR Account
658: if (StringUtils.isNotEmpty(a21
659: .getIndirectCostRecoveryChartOfAccountsCode())
660: && StringUtils.isNotEmpty(a21
661: .getIndirectCostRecoveryAccountNumber())) {
662: if (ObjectUtils
663: .isNull(a21.getIndirectCostRecoveryAccount())) {
664: putFieldError(
665: "a21SubAccount.indirectCostRecoveryAccountNumber",
666: KFSKeyConstants.ERROR_EXISTENCE,
667: "ICR Account: "
668: + a21
669: .getIndirectCostRecoveryChartOfAccountsCode()
670: + "-"
671: + a21
672: .getIndirectCostRecoveryAccountNumber());
673: }
674: }
675:
676: // The cost sharing fields must be empty if the sub-account type code is for ICR
677: if (checkCgCostSharingIsEmpty() == false) {
678: putFieldError(
679: "a21SubAccount.costShareChartOfAccountCode",
680: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_COST_SHARE_SECTION_INVALID,
681: a21.getSubAccountTypeCode());
682:
683: success &= false;
684: }
685:
686: return success;
687: }
688:
689: /**
690: * This method tests if all fields in the Cost Sharing section are empty.
691: *
692: * @return true if the cost sharing values passed in are empty, otherwise false.
693: */
694: protected boolean checkCgCostSharingIsEmpty() {
695:
696: boolean success = true;
697:
698: A21SubAccount newA21SubAccount = newSubAccount
699: .getA21SubAccount();
700:
701: success &= StringUtils.isEmpty(newA21SubAccount
702: .getCostShareChartOfAccountCode());
703: success &= StringUtils.isEmpty(newA21SubAccount
704: .getCostShareSourceAccountNumber());
705: success &= StringUtils.isEmpty(newA21SubAccount
706: .getCostShareSourceSubAccountNumber());
707:
708: return success;
709: }
710:
711: /**
712: * This method tests if all fields in the ICR section are empty.
713: *
714: * @return true if the ICR values passed in are empty, otherwise false.
715: */
716: protected boolean checkCgIcrIsEmpty() {
717:
718: boolean success = true;
719:
720: A21SubAccount newA21SubAccount = newSubAccount
721: .getA21SubAccount();
722:
723: success &= StringUtils.isEmpty(newA21SubAccount
724: .getFinancialIcrSeriesIdentifier());
725: success &= StringUtils.isEmpty(newA21SubAccount
726: .getIndirectCostRecoveryChartOfAccountsCode());
727: success &= StringUtils.isEmpty(newA21SubAccount
728: .getIndirectCostRecoveryAccountNumber());
729: success &= StringUtils.isEmpty(newA21SubAccount
730: .getIndirectCostRecoveryTypeCode());
731: // this is a boolean, so create any value if set to true, meaning a user checked the box, otherwise assume it's empty
732: success &= StringUtils.isEmpty(newA21SubAccount
733: .getOffCampusCode() ? "1" : "");
734:
735: return success;
736: }
737:
738: /**
739: * This method tests whether the specified user is part of the group that grants authorization to the CG fields.
740: *
741: * @param user - the user to test
742: * @return true if user is part of the group, false otherwise
743: */
744: protected boolean isCgAuthorized(UniversalUser user) {
745:
746: // attempt to get the group name that grants access to the CG fields
747: String allowedCgWorkgroup = SpringContext
748: .getBean(ParameterService.class)
749: .getParameterValue(
750: SubAccount.class,
751: KFSConstants.ChartApcParms.SUBACCOUNT_CG_WORKGROUP_PARM_NAME);
752:
753: if (user.isMember(allowedCgWorkgroup)) {
754: LOG.info("User '" + user.getPersonUserIdentifier()
755: + "' is a member of the group '"
756: + allowedCgWorkgroup
757: + "', which gives them access to the CG fields.");
758: return true;
759: } else {
760: LOG.info("User '" + user.getPersonUserIdentifier()
761: + "' is not a member of the group '"
762: + allowedCgWorkgroup
763: + "', so they have no access to the CG fields.");
764: return false;
765: }
766: }
767:
768: /**
769: * This method tests the value entered, and if there is anything there it logs a new error, and returns false.
770: *
771: * @param value - String value to be tested
772: * @param fieldName - name of the field being tested
773: * @return false if there is any value in value, otherwise true
774: */
775: protected boolean disallowAnyValues(String value, String fieldName) {
776: if (StringUtils.isNotEmpty(value)) {
777: putFieldError(
778: fieldName,
779: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NOT_AUTHORIZED_ENTER_CG_FIELDS,
780: getDisplayName(fieldName));
781: return false;
782: }
783: return true;
784: }
785:
786: /**
787: * This method tests the two values entered, and if there is any change between the two, it logs an error, and returns false.
788: * Note that the comparison is done after trimming both leading and trailing whitespace from both strings, and then doing a
789: * case-insensitive comparison.
790: *
791: * @param oldValue - the original String value of the field
792: * @param newValue - the new String value of the field
793: * @param fieldName - name of the field being tested
794: * @return false if there is any difference between the old and new, true otherwise
795: */
796: protected boolean disallowChangedValues(String oldValue,
797: String newValue, String fieldName) {
798:
799: if (isFieldValueChanged(oldValue, newValue)) {
800: putFieldError(
801: fieldName,
802: KFSKeyConstants.ERROR_DOCUMENT_SUBACCTMAINT_NOT_AUTHORIZED_CHANGE_CG_FIELDS,
803: getDisplayName(fieldName));
804: return false;
805: }
806: return true;
807: }
808:
809: /**
810: *
811: * This compares two string values to see if the newValue has changed from the oldValue
812: * @param oldValue - original value
813: * @param newValue - new value
814: * @return true if the two fields are different from each other
815: */
816: protected boolean isFieldValueChanged(String oldValue,
817: String newValue) {
818:
819: if (StringUtils.isBlank(oldValue)
820: && StringUtils.isBlank(newValue)) {
821: return false;
822: }
823:
824: if (StringUtils.isBlank(oldValue)
825: && StringUtils.isNotBlank(newValue)) {
826: return true;
827: }
828:
829: if (StringUtils.isNotBlank(oldValue)
830: && StringUtils.isBlank(newValue)) {
831: return true;
832: }
833:
834: if (!oldValue.trim().equalsIgnoreCase(newValue.trim())) {
835: return true;
836: }
837:
838: return false;
839: }
840:
841: /**
842: *
843: * This method retrieves the label name for a specific property
844: * @param propertyName - property to retrieve label for (from the DD)
845: * @return the label
846: */
847: private String getDisplayName(String propertyName) {
848: return getDdService().getAttributeLabel(SubAccount.class,
849: propertyName);
850: }
851:
852: protected void setCgAuthorized(boolean cgAuthorized) {
853: this .cgAuthorized = cgAuthorized;
854: }
855:
856: protected boolean getCgAuthorized() {
857: return cgAuthorized;
858: }
859:
860: }
|