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.sql.Timestamp;
019: import java.util.Calendar;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import org.apache.commons.lang.time.DateUtils;
025: import org.kuali.core.bo.DocumentType;
026: import org.kuali.core.bo.user.UniversalUser;
027: import org.kuali.core.document.MaintenanceDocument;
028: import org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase;
029: import org.kuali.core.util.KualiDecimal;
030: import org.kuali.core.util.ObjectUtils;
031: import org.kuali.kfs.KFSConstants;
032: import org.kuali.kfs.KFSKeyConstants;
033: import org.kuali.kfs.context.SpringContext;
034: import org.kuali.kfs.service.ParameterService;
035: import org.kuali.module.chart.bo.Account;
036: import org.kuali.module.chart.bo.ChartUser;
037: import org.kuali.module.chart.bo.Delegate;
038:
039: /**
040: * Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route
041: * event.
042: */
043: public class DelegateRule extends MaintenanceDocumentRuleBase {
044:
045: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
046: .getLogger(DelegateRule.class);
047: private static final KualiDecimal ZERO = new KualiDecimal(0);
048:
049: private Delegate oldDelegate;
050: private Delegate newDelegate;
051:
052: /**
053: * Constructs a DelegateRule.java.
054: */
055: public DelegateRule() {
056: super ();
057: }
058:
059: /**
060: * This runs specific rules that are called when a document is saved:
061: * <ul>
062: * <li>{@link DelegateRule#checkSimpleRules()}</li>
063: * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
064: * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
065: * </ul>
066: *
067: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
068: * @return doesn't fail on save, even if sub-rules fail
069: */
070: @Override
071: protected boolean processCustomSaveDocumentBusinessRules(
072: MaintenanceDocument document) {
073:
074: LOG.info("Entering processCustomSaveDocumentBusinessRules()");
075: setupConvenienceObjects(document);
076:
077: // check simple rules
078: checkSimpleRules();
079:
080: // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
081: checkOnlyOnePrimaryRoute(document);
082:
083: // delegate user must be Active and Professional
084: checkDelegateUserRules(document);
085:
086: return true;
087: }
088:
089: /**
090: * This runs specific rules that are called when a document is routed:
091: * <ul>
092: * <li>{@link DelegateRule#checkSimpleRules()}</li>
093: * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
094: * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
095: * </ul>
096: *
097: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
098: * @return fails if sub-rules fail
099: */
100: protected boolean processCustomRouteDocumentBusinessRules(
101: MaintenanceDocument document) {
102:
103: boolean success = true;
104:
105: LOG.info("Entering processCustomRouteDocumentBusinessRules()");
106: setupConvenienceObjects(document);
107:
108: // check simple rules
109: success &= checkSimpleRules();
110:
111: // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
112: success &= checkOnlyOnePrimaryRoute(document);
113:
114: // delegate user must be Active and Professional
115: success &= checkDelegateUserRules(document);
116:
117: return success;
118: }
119:
120: /**
121: * This runs specific rules that are called when a document is approved:
122: * <ul>
123: * <li>{@link DelegateRule#checkSimpleRules()}</li>
124: * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
125: * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
126: * </ul>
127: *
128: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
129: */
130: protected boolean processCustomApproveDocumentBusinessRules(
131: MaintenanceDocument document) {
132:
133: boolean success = true;
134:
135: LOG
136: .info("Entering processCustomApproveDocumentBusinessRules()");
137: setupConvenienceObjects(document);
138:
139: // check simple rules
140: success &= checkSimpleRules();
141:
142: success &= checkOnlyOnePrimaryRoute(document);
143:
144: // delegate user must be Active and Professional
145: success &= checkDelegateUserRules(document);
146:
147: return success;
148: }
149:
150: /**
151: * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
152: * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
153: * all sub-objects from the DB by their primary keys, if available.
154: *
155: * @param document - the maintenanceDocument being evaluated
156: */
157: protected void setupConvenienceObjects(MaintenanceDocument document) {
158:
159: // setup oldAccount convenience objects, make sure all possible sub-objects are populated
160: oldDelegate = (Delegate) super .getOldBo();
161:
162: // setup newAccount convenience objects, make sure all possible sub-objects are populated
163: newDelegate = (Delegate) super .getNewBo();
164: }
165:
166: /**
167: * This checks to see if
168: * <ul>
169: * <li>the delegate start date is valid and they are active</li>
170: * <li>from amount is >= 0</li>
171: * <li>to amount cannot be empty when from amount is filled out</li>
172: * <li>to amount is >= from amount</li>
173: * <li>account cannot be closed</li>
174: * </ul>
175: *
176: * @return
177: */
178: protected boolean checkSimpleRules() {
179:
180: boolean success = true;
181: boolean newActive;
182: KualiDecimal fromAmount = newDelegate
183: .getFinDocApprovalFromThisAmt();
184: KualiDecimal toAmount = newDelegate
185: .getFinDocApprovalToThisAmount();
186: newActive = newDelegate.isAccountDelegateActiveIndicator();
187:
188: // start date must be greater than or equal to today if active
189: if ((ObjectUtils.isNotNull(newDelegate
190: .getAccountDelegateStartDate()))
191: && newActive) {
192: Timestamp today = getDateTimeService()
193: .getCurrentTimestamp();
194: today.setTime(DateUtils.truncate(today,
195: Calendar.DAY_OF_MONTH).getTime());
196: if (newDelegate.getAccountDelegateStartDate().before(today)) {
197: putFieldError(
198: "accountDelegateStartDate",
199: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_STARTDATE_IN_PAST);
200: success &= false;
201: }
202: }
203:
204: // FROM amount must be >= 0 (may not be negative)
205: if (ObjectUtils.isNotNull(fromAmount)) {
206: if (fromAmount.isLessThan(new KualiDecimal(0))) {
207: putFieldError(
208: "finDocApprovalFromThisAmt",
209: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
210: success &= false;
211: }
212: }
213:
214: if (ObjectUtils.isNotNull(fromAmount)
215: && ObjectUtils.isNull(toAmount)) {
216: putFieldError(
217: "finDocApprovalToThisAmount",
218: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
219: success &= false;
220: }
221:
222: // TO amount must be >= FROM amount or Zero
223: if (ObjectUtils.isNotNull(toAmount)) {
224:
225: if (ObjectUtils.isNull(fromAmount)) {
226: // case if FROM amount is null then TO amount must be zero
227: if (!toAmount.equals(new KualiDecimal(0))) {
228: putFieldError(
229: "finDocApprovalToThisAmount",
230: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
231: success &= false;
232: }
233: } else {
234: // case if FROM amount is non-null and positive, disallow TO amount being less
235: // if to amount is zero it is considered infinite (fromAmount -> infinity)
236: if (toAmount.isLessThan(fromAmount)
237: && !toAmount.equals(ZERO)) {
238: putFieldError(
239: "finDocApprovalToThisAmount",
240: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
241: success &= false;
242: }
243: }
244: }
245:
246: // the account that has been chosen cannot be closed
247: Account account = newDelegate.getAccount();
248: if (ObjectUtils.isNotNull(account)) {
249: if (account.isAccountClosedIndicator()) {
250: putFieldError(
251: "accountNumber",
252: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_ACCT_NOT_CLOSED);
253: success &= false;
254: }
255: }
256:
257: return success;
258: }
259:
260: /**
261: * This checks to see if there is already a record for the primary route
262: *
263: * @param document
264: * @return false if there is a record
265: */
266: protected boolean checkOnlyOnePrimaryRoute(
267: MaintenanceDocument document) {
268:
269: boolean success = true;
270: boolean checkDb = false;
271: boolean newPrimary;
272: boolean newActive;
273: boolean blockingDocumentExists;
274: DocumentType documentType;
275:
276: // exit out immediately if this doc is not requesting a primary route
277: newPrimary = newDelegate.isAccountsDelegatePrmrtIndicator();
278: if (!newPrimary) {
279: return success;
280: }
281:
282: // exit if new document not active
283: newActive = newDelegate.isAccountDelegateActiveIndicator();
284: if (!newActive) {
285: return success;
286: }
287:
288: // exit if document group corresponding to document type = "EX"
289: documentType = newDelegate.getDocumentType();
290: if (ObjectUtils.isNotNull(documentType)) {
291: if ((documentType.getFinancialDocumentGroupCode())
292: .equals("EX")) {
293: return success;
294: }
295: }
296:
297: // if its a new document, we are only interested if they have chosen this one
298: // to be a primary route
299: if (document.isNew()) {
300: if (newPrimary) {
301: checkDb = true;
302: }
303: }
304:
305: // handle an edit, where all we care about is that someone might change it
306: // from NOT a primary TO a primary
307: if (document.isEdit()) {
308: boolean oldPrimary = oldDelegate
309: .isAccountsDelegatePrmrtIndicator();
310: if (!oldPrimary && newPrimary) {
311: checkDb = true;
312: }
313: }
314:
315: // if we dont want to check the db for another primary, then exit
316: if (!checkDb) {
317: return success;
318: }
319:
320: // if a primary already exists for a document type (including ALL), throw an error. However, current business rules
321: // should allow a primary for other document types, even if a primary for ALL already exists.
322:
323: Map whereMap = new HashMap();
324: whereMap.put("chartOfAccountsCode", newDelegate
325: .getChartOfAccountsCode());
326: whereMap.put("accountNumber", newDelegate.getAccountNumber());
327: whereMap.put("accountsDelegatePrmrtIndicator", Boolean
328: .valueOf(true));
329: whereMap.put("financialDocumentTypeCode", newDelegate
330: .getFinancialDocumentTypeCode());
331: whereMap.put("accountDelegateActiveIndicator", Boolean
332: .valueOf(true));
333:
334: // find all the matching records
335: Collection primaryRoutes = getBoService().findMatching(
336: Delegate.class, whereMap);
337:
338: // if there is at least one result, then this business rule is tripped
339: if (primaryRoutes.size() > 0) {
340: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
341: success &= false;
342: }
343:
344: return success;
345: }
346:
347: /**
348: * This checks to see if the user is valid and active for this module
349: *
350: * @param document
351: * @return false if this user is not valid or active for being a delegate
352: */
353: protected boolean checkDelegateUserRules(
354: MaintenanceDocument document) {
355:
356: boolean success = true;
357:
358: // if the user doesnt exist, then do nothing, it'll fail the existence test elsewhere
359: if (ObjectUtils.isNull(newDelegate.getAccountDelegate())) {
360: return success;
361: }
362: UniversalUser user = newDelegate.getAccountDelegate();
363:
364: // user must be an active kuali user
365: if (!user.isActiveForModule(ChartUser.MODULE_ID)) {
366: success = false;
367: putFieldError(
368: "accountDelegate.personUserIdentifier",
369: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_NOT_ACTIVE_KUALI_USER);
370: }
371:
372: // user must be of the allowable statuses (A - Active)
373: if (!SpringContext
374: .getBean(ParameterService.class)
375: .getParameterEvaluator(
376: Delegate.class,
377: KFSConstants.ChartApcParms.DELEGATE_USER_EMP_STATUSES,
378: user.getEmployeeStatusCode())
379: .evaluationSucceeds()) {
380: success = false;
381: putFieldError(
382: "accountDelegate.personUserIdentifier",
383: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_NOT_ACTIVE);
384: }
385:
386: // user must be of the allowable types (P - Professional)
387: if (!SpringContext
388: .getBean(ParameterService.class)
389: .getParameterEvaluator(
390: Delegate.class,
391: KFSConstants.ChartApcParms.DELEGATE_USER_EMP_TYPES,
392: user.getEmployeeTypeCode())
393: .evaluationSucceeds()) {
394: success = false;
395: putFieldError(
396: "accountDelegate.personUserIdentifier",
397: KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_NOT_PROFESSIONAL);
398: }
399:
400: return success;
401: }
402: }
|