001: /*
002: * Copyright 2005-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.List;
021: import java.util.Map;
022: import java.util.Set;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.apache.ojb.broker.PersistenceBrokerException;
026: import org.kuali.core.document.MaintenanceDocument;
027: import org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase;
028: import org.kuali.core.service.KualiConfigurationService;
029: import org.kuali.kfs.KFSConstants;
030: import org.kuali.kfs.KFSKeyConstants;
031: import org.kuali.kfs.context.SpringContext;
032: import org.kuali.kfs.service.ParameterService;
033: import org.kuali.module.chart.bo.ObjLevel;
034: import org.kuali.module.chart.bo.ObjectCode;
035: import org.kuali.module.chart.bo.ObjectCons;
036: import org.kuali.module.chart.bo.codes.BudgetAggregationCode;
037: import org.kuali.module.chart.service.ChartService;
038: import org.kuali.module.chart.service.ObjectCodeService;
039: import org.kuali.module.chart.service.ObjectConsService;
040: import org.kuali.module.chart.service.ObjectLevelService;
041: import org.kuali.module.financial.service.UniversityDateService;
042:
043: /**
044: * This class implements the business rules for {@link ObjectCode}
045: */
046: public class ObjectCodeRule extends MaintenanceDocumentRuleBase {
047:
048: private static ObjectLevelService objectLevelService;
049: private static ObjectCodeService objectCodeService;
050: private static ObjectConsService objectConsService;
051:
052: private static KualiConfigurationService configService;
053: private static ChartService chartService;
054: private Map reportsTo;
055: private static List illegalValues;
056:
057: /**
058: *
059: * Constructs a ObjectCodeRule
060: * Pseudo-injects some services as well as fills out the reports to chart hierarchy
061: */
062: public ObjectCodeRule() {
063:
064: if (objectConsService == null) {
065: configService = SpringContext
066: .getBean(KualiConfigurationService.class);
067:
068: illegalValues = retrieveParameterSet(KFSConstants.ChartApcParms.OBJECT_CODE_ILLEGAL_VALUES);
069:
070: objectLevelService = SpringContext
071: .getBean(ObjectLevelService.class);
072: objectCodeService = SpringContext
073: .getBean(ObjectCodeService.class);
074: chartService = SpringContext.getBean(ChartService.class);
075: objectConsService = SpringContext
076: .getBean(ObjectConsService.class);
077: }
078: reportsTo = chartService.getReportsToHierarchy();
079: }
080:
081: /**
082: * This method calls the following rules on document save:
083: * <ul>
084: * <li>{@link ObjectCodeRule#processObjectCodeRules(ObjectCode)}</li>
085: * </ul>
086: * It does not fail if rules fail
087: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
088: */
089: @Override
090: protected boolean processCustomSaveDocumentBusinessRules(
091: MaintenanceDocument document) {
092:
093: // default to success
094: boolean success = true;
095:
096: Object maintainableObject = document.getNewMaintainableObject()
097: .getBusinessObject();
098:
099: success &= processObjectCodeRules((ObjectCode) maintainableObject);
100:
101: return success;
102:
103: }
104:
105: /**
106: * This method calls the following rules on document route:
107: * <ul>
108: * <li>{@link ObjectCodeRule#processObjectCodeRules(ObjectCode)}</li>
109: * </ul>
110: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
111: */
112: @Override
113: protected boolean processCustomRouteDocumentBusinessRules(
114: MaintenanceDocument document) {
115: LOG.debug("processCustomRouteDocumentBusinessRules called");
116:
117: boolean success = true;
118:
119: Object maintainableObject = document.getNewMaintainableObject()
120: .getBusinessObject();
121: success &= processObjectCodeRules((ObjectCode) maintainableObject);
122:
123: return success;
124: }
125:
126: /**
127: *
128: * This checks the following rules:
129: * <ul>
130: * <li>object code valid</li>
131: * <li>reports to chart code is valid (similar to what {@link ObjectCodePreRules} does)</li>
132: * <li>is the budget aggregation code valid</li>
133: * <li>then checks to make sure that this object code hasn't already been entered in the consolidation and level table</li>
134: * <li>finally checks to make sure that the next year object code (if filled out) isn't already in there and that this object code has a valid fiscal year</li>
135: * </ul>
136: * @param objectCode
137: * @return
138: */
139: private boolean processObjectCodeRules(ObjectCode objectCode) {
140:
141: boolean result = true;
142:
143: String objCode = objectCode.getFinancialObjectCode();
144:
145: if (!isLegalObjectCode(objCode)) {
146: this .putFieldError("financialObjectCode",
147: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_ILLEGAL,
148: objCode);
149: result = false;
150: }
151:
152: Integer year = objectCode.getUniversityFiscalYear();
153: String chartCode = objectCode.getChartOfAccountsCode();
154: String calculatedReportsToChartCode;
155: String reportsToObjectCode = objectCode
156: .getReportsToFinancialObjectCode();
157: String nextYearObjectCode = objectCode
158: .getNextYearFinancialObjectCode();
159:
160: // only validate if chartCode is NOT null ( chartCode should be provided to get determine reportsToChartCode )
161: if (chartCode != null) {
162:
163: // We must calculate a reportsToChartCode here to duplicate the logic
164: // that takes place in the preRule.
165: // The reason is that when we do a SAVE, the pre-rules are not
166: // run and we will get bogus error messages.
167: // So, we are simulating here what the pre-rule will do.
168: calculatedReportsToChartCode = (String) reportsTo
169: .get(chartCode);
170:
171: if (!verifyReportsToChartCode(year, chartCode, objectCode
172: .getFinancialObjectCode(),
173: calculatedReportsToChartCode, reportsToObjectCode)) {
174: this
175: .putFieldError(
176: "reportsToFinancialObjectCode",
177: KFSKeyConstants.ERROR_DOCUMENT_REPORTS_TO_OBJCODE_ILLEGAL,
178: new String[] { reportsToObjectCode,
179: calculatedReportsToChartCode });
180: result = false;
181: }
182: }
183:
184: String budgetAggregationCode = objectCode
185: .getFinancialBudgetAggregationCd();
186:
187: if (!isLegalBudgetAggregationCode(budgetAggregationCode)) {
188: this
189: .putFieldError(
190: "financialBudgetAggregationCd",
191: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_MUST_ONEOF_VALID,
192: "Budget Aggregation Code");
193: result = false;
194: }
195:
196: objectCode.refresh();
197:
198: // Chart code (fin_coa_cd) must be valid - handled by dd
199:
200: if (!this .consolidationTableDoesNotHave(chartCode, objCode)) {
201: this
202: .putFieldError(
203: "financialObjectCode",
204: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_CONSOLIDATION_ERROR,
205: chartCode + "-" + objCode);
206: result = false;
207: }
208:
209: if (!this .objectLevelTableDoesNotHave(chartCode, objCode)) {
210: this .putFieldError("financialObjectCode",
211: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_LEVEL_ERROR,
212: chartCode + "-" + objCode);
213: result = false;
214: }
215: if (!StringUtils.isEmpty(nextYearObjectCode)
216: && nextYearObjectCodeDoesNotExistThisYear(year,
217: chartCode, nextYearObjectCode)) {
218: this
219: .putFieldError(
220: "nextYearFinancialObjectCode",
221: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_MUST_BEVALID,
222: "Next Year Object Code");
223: result = false;
224: }
225: if (!this .isValidYear(year)) {
226: this
227: .putFieldError(
228: "universityFiscalYear",
229: KFSKeyConstants.ERROR_DOCUMENT_OBJCODE_MUST_BEVALID,
230: "Fiscal Year");
231: }
232:
233: /*
234: * The framework handles this: Pending object must not have duplicates waiting for approval Description (fdoc_desc) must be
235: * entered Verify the DD handles these: Fiscal year (univ_fisal_yr) must be entered Chart code (fin_coa_code) must be
236: * entered Object code (fin_object_code) must be entered (fin_obj_cd_nm) must be entered (fin_obj_cd_shrt_nm) must be
237: * entered Object level (obj_level_code) must be entered The Reports to Object (rpts_to_fin_obj_cd) must be entered It seems
238: * like these are Business Rules for other objects: An Object code must be active when it is used as valid value in the
239: * Labor Object Code table An Object code must be active when it is used as valid value in the LD Benefits Calculation table
240: * An Object code must be active when it is used as valid value in the ICR Automated Entry table An Object code must be
241: * active when it is used as valid value in the Chart table These still need attention: Warning if chart code is inactive
242: * Warning if object level is inactive If the Next Year Object has been entered, it must exist in the object code table
243: * alongside the fiscal year and chart code (rpts_to_fin_coa_cd) is looked up based on chart code [fp_hcoat]
244: */
245:
246: return result;
247:
248: }
249:
250: /**
251: * This method checks whether newly added object code already exists in Object Level table
252: *
253: * @param chartCode
254: * @param objectCode
255: * @return false if this object code already exists in the object level table
256: */
257: public boolean objectLevelTableDoesNotHave(String chartCode,
258: String objectCode) {
259: try {
260: ObjLevel objLevel = objectLevelService.getByPrimaryId(
261: chartCode, objectCode);
262: if (objLevel != null) {
263: objLevel.getFinancialObjectLevelCode(); // this might throw an Exception when proxying is in effect
264: return false;
265: }
266: } catch (PersistenceBrokerException e) {
267: // intentionally ignore the Exception
268: }
269:
270: return true;
271: }
272:
273: /**
274: *
275: * This Check whether newly added object code already exists in Consolidation table
276: * @param chartCode
277: * @param objectCode
278: * @return false if this object code already exists in the object consolidation table
279: */
280: public boolean consolidationTableDoesNotHave(String chartCode,
281: String objectCode) {
282: try {
283: ObjectCons objectCons = objectConsService.getByPrimaryId(
284: chartCode, objectCode);
285: if (objectCons != null) {
286: objectCons.getFinConsolidationObjectCode(); // this might throw an Exception when proxying is in effect
287: return false;
288: }
289: } catch (PersistenceBrokerException e) {
290: // intentionally ignore the Exception
291: }
292: return true;
293: }
294:
295: /**
296: *
297: * This checks to see if the next year object code already exists in the next fiscal year
298: * @param year
299: * @param chartCode
300: * @param objCode
301: * @return false if this object code exists in the next fiscal year
302: */
303: public boolean nextYearObjectCodeDoesNotExistThisYear(Integer year,
304: String chartCode, String objCode) {
305: try {
306: ObjectCode objectCode = objectCodeService.getByPrimaryId(
307: year, chartCode, objCode);
308: if (objectCode != null) {
309: return false;
310: }
311: } catch (PersistenceBrokerException e) {
312: // intentionally ignore the Exception
313: }
314: return true;
315: }
316:
317: /**
318: *
319: * This checks to make sure the fiscal year they are trying to assign is valid
320: * @param year
321: * @return true if this is a valid year
322: */
323: public boolean isValidYear(Integer year) {
324: if (year == null)
325: return false;
326: int enteredYear = year.intValue();
327: int currentYear = SpringContext.getBean(
328: UniversityDateService.class).getCurrentFiscalYear()
329: .intValue();
330: if ((enteredYear - currentYear) == 0
331: || (enteredYear - currentYear) == 1)
332: return true;
333: return false;
334: }
335:
336: /**
337: * This method is a null-safe wrapper around Set.contains().
338: *
339: * @param set - methods returns false if the Set is null
340: * @param value to seek
341: * @return true iff Set exists and contains given value
342: */
343: protected boolean permitted(Set set, Object value) {
344: if (set != null) {
345: return set.contains(value);
346: }
347: return false;
348: }
349:
350: /**
351: *
352: * This method is a null-safe wrapper around Set.contains()
353: * @param set
354: * @param value
355: * @return true if this value is not contained in the Set or Set is null
356: */
357: protected boolean denied(List set, Object value) {
358: if (set != null) {
359: return !set.contains(value);
360: }
361: return true;
362: }
363:
364: /**
365: * Object code (fin_obj_code) must not have an institutionally specified illegal value
366: *
367: * @return
368: */
369: protected boolean isLegalObjectCode(String objectCode) {
370: boolean result = denied(illegalValues, objectCode);
371: return result;
372: }
373:
374: /**
375: * Budget Aggregation Code (fobj_bdgt_aggr_cd) must have an institutionally specified value
376: *
377: * @param budgetAggregationCode
378: * @return true if this is a legal budget aggregation code
379: */
380: protected boolean isLegalBudgetAggregationCode(
381: String budgetAggregationCode) {
382:
383: // find all the matching records
384: Map whereMap = new HashMap();
385: whereMap.put("code", budgetAggregationCode);
386:
387: Collection budgetAggregationCodes = getBoService()
388: .findMatching(BudgetAggregationCode.class, whereMap);
389:
390: // if there is at least one result, then entered budget aggregation code is legal
391: return budgetAggregationCodes.size() > 0;
392: }
393:
394: /**
395: *
396: * This checks to see if the object code already exists in the system
397: * @param year
398: * @param chart
399: * @param objectCode
400: * @return true if it exists
401: */
402: protected boolean verifyObjectCode(Integer year, String chart,
403: String objectCode) {
404: return null != objectCodeService.getByPrimaryId(year, chart,
405: objectCode);
406: }
407:
408: /**
409: *
410: * This method checks When the value of reportsToChartCode does not have an institutional exception, the Reports to Object
411: * (rpts_to_fin_obj_cd) fiscal year, and chart code must exist in the object code table
412: * if the chart and object are the same, then skip the check
413: * this assumes that the validity of the reports-to object code has already been tested (and corrected if necessary)
414: * @param year
415: * @param chart
416: * @param objectCode
417: * @param reportsToChartCode
418: * @param reportsToObjectCode
419: * @return true if the object code's reports to chart and chart are the same and reports to object and object code are the same
420: * or if the object code already exists
421: */
422: protected boolean verifyReportsToChartCode(Integer year,
423: String chart, String objectCode, String reportsToChartCode,
424: String reportsToObjectCode) {
425: // TODO: verify this ambiguously stated rule against the UNIFACE source
426: // When the value of reportsToChartCode does not have an institutional exception, the Reports to Object
427: // (rpts_to_fin_obj_cd) fiscal year, and chart code must exist in the object code table
428:
429: // if the chart and object are the same, then skip the check
430: // this assumes that the validity of the reports-to object code has already been tested (and corrected if necessary)
431: if (StringUtils.equals(reportsToChartCode, chart)
432: && StringUtils.equals(reportsToObjectCode, objectCode)) {
433: return true;
434: }
435:
436: // otherwise, check if the object is valid
437: return verifyObjectCode(year, reportsToChartCode,
438: reportsToObjectCode);
439: }
440:
441: /**
442: *
443: * This method retrieves the list of {@link org.kuali.core.bo.Parameter} for the {@link ObjectCode} and specific parameterName
444: * @param parameterName
445: * @return List of parameters
446: */
447: private List<String> retrieveParameterSet(String parameterName) {
448: return SpringContext.getBean(ParameterService.class)
449: .getParameterValues(ObjectCode.class, parameterName);
450: }
451:
452: }
|