0001: /*
0002: * Copyright 2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.purap.rules;
0017:
0018: import static org.kuali.kfs.KFSConstants.ACCOUNTING_LINE_ERRORS;
0019: import static org.kuali.kfs.KFSConstants.AMOUNT_PROPERTY_NAME;
0020: import static org.kuali.kfs.KFSConstants.BALANCE_TYPE_ACTUAL;
0021: import static org.kuali.kfs.KFSConstants.BLANK_SPACE;
0022: import static org.kuali.kfs.KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS;
0023: import static org.kuali.kfs.KFSConstants.SOURCE_ACCOUNTING_LINE_ERROR_PATTERN;
0024: import static org.kuali.kfs.KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS;
0025: import static org.kuali.kfs.KFSConstants.TARGET_ACCOUNTING_LINE_ERROR_PATTERN;
0026: import static org.kuali.kfs.KFSConstants.ZERO;
0027: import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD;
0028: import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE;
0029: import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE;
0030: import static org.kuali.kfs.KFSKeyConstants.ERROR_ACCOUNTINGLINE_LASTACCESSIBLE_DELETE;
0031: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_INVALID_FORMAT;
0032: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_MAX_LENGTH;
0033: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_BALANCE;
0034: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_FUND_GROUP_SET_DOES_NOT_BALANCE;
0035: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_OPTIONAL_ONE_SIDED_DOCUMENT_REQUIRED_NUMBER_OF_ACCOUNTING_LINES_NOT_MET;
0036: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_SINGLE_ACCOUNTING_LINE_SECTION_TOTAL_CHANGED;
0037: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_SOURCE_SECTION_NO_ACCOUNTING_LINES;
0038: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_TARGET_SECTION_NO_ACCOUNTING_LINES;
0039: import static org.kuali.kfs.KFSKeyConstants.ERROR_INVALID_FORMAT;
0040: import static org.kuali.kfs.KFSKeyConstants.ERROR_INVALID_NEGATIVE_AMOUNT_NON_CORRECTION;
0041: import static org.kuali.kfs.KFSKeyConstants.ERROR_MAX_LENGTH;
0042: import static org.kuali.kfs.KFSKeyConstants.ERROR_ZERO_AMOUNT;
0043: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_FUND_GROUP_CODES;
0044: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_CODES;
0045: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_CONSOLIDATIONS;
0046: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_LEVELS;
0047: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_SUB_TYPE_CODES;
0048: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_OBJECT_TYPE_CODES;
0049: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER.RESTRICTED_SUB_FUND_GROUP_CODES;
0050:
0051: import java.lang.reflect.InvocationTargetException;
0052: import java.sql.Timestamp;
0053: import java.util.ArrayList;
0054: import java.util.Arrays;
0055: import java.util.Iterator;
0056: import java.util.List;
0057: import java.util.ListIterator;
0058:
0059: import org.apache.commons.beanutils.PropertyUtils;
0060: import org.apache.commons.lang.StringUtils;
0061: import org.kuali.core.datadictionary.BusinessObjectEntry;
0062: import org.kuali.core.document.Document;
0063: import org.kuali.core.exceptions.ValidationException;
0064: import org.kuali.core.rule.event.ApproveDocumentEvent;
0065: import org.kuali.core.rule.event.BlanketApproveDocumentEvent;
0066: import org.kuali.core.service.DataDictionaryService;
0067: import org.kuali.core.service.DateTimeService;
0068: import org.kuali.core.service.DictionaryValidationService;
0069: import org.kuali.core.service.DocumentService;
0070: import org.kuali.core.service.DocumentTypeService;
0071: import org.kuali.core.util.ErrorMessage;
0072: import org.kuali.core.util.ExceptionUtils;
0073: import org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper;
0074: import org.kuali.core.util.GlobalVariables;
0075: import org.kuali.core.util.KualiDecimal;
0076: import org.kuali.core.util.ObjectUtils;
0077: import org.kuali.core.web.format.CurrencyFormatter;
0078: import org.kuali.core.workflow.service.KualiWorkflowDocument;
0079: import org.kuali.kfs.KFSConstants;
0080: import org.kuali.kfs.KFSKeyConstants;
0081: import org.kuali.kfs.KFSPropertyConstants;
0082: import org.kuali.kfs.bo.AccountingLine;
0083: import org.kuali.kfs.bo.GeneralLedgerPendingEntry;
0084: import org.kuali.kfs.bo.SourceAccountingLine;
0085: import org.kuali.kfs.context.SpringContext;
0086: import org.kuali.kfs.document.AccountingDocument;
0087: import org.kuali.kfs.rule.AddAccountingLineRule;
0088: import org.kuali.kfs.rule.DeleteAccountingLineRule;
0089: import org.kuali.kfs.rule.GenerateGeneralLedgerPendingEntriesRule;
0090: import org.kuali.kfs.rule.ReviewAccountingLineRule;
0091: import org.kuali.kfs.rule.SufficientFundsCheckingPreparationRule;
0092: import org.kuali.kfs.rule.UpdateAccountingLineRule;
0093: import org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants;
0094: import org.kuali.kfs.rules.AccountingDocumentRuleUtil;
0095: import org.kuali.kfs.rules.GeneralLedgerPostingDocumentRuleBase;
0096: import org.kuali.kfs.service.GeneralLedgerPendingEntryService;
0097: import org.kuali.kfs.service.HomeOriginationService;
0098: import org.kuali.kfs.service.OptionsService;
0099: import org.kuali.kfs.service.ParameterEvaluator;
0100: import org.kuali.kfs.service.ParameterService;
0101: import org.kuali.kfs.service.impl.ParameterConstants;
0102: import org.kuali.module.chart.bo.ChartUser;
0103: import org.kuali.module.chart.bo.ObjectCode;
0104: import org.kuali.module.gl.service.SufficientFundsService;
0105: import org.kuali.module.purap.util.PurapAccountingLineRuleUtil;
0106:
0107: import edu.iu.uis.eden.exception.WorkflowException;
0108:
0109: /**
0110: * NOTE: COPIED FROM AccountingDocumentRuleBase VERSION 1.27, COMPARE WITH THAT FILE FOR DIFF WITH BASE NOTE: DO NOT MAKE CHANGES TO
0111: * THIS FILE UNLESS ABSOLUTELY NECESSARY THIS SHOULD NOT CONTAIN PURAP SPECIFIC CODE This class contains all of the business rules
0112: * that are common to all of the Financial Transaction Processing documents. Any document specific business rules are contained
0113: * within the specific child class that extends off of this one.
0114: */
0115: public abstract class PurapAccountingDocumentRuleBase extends
0116: GeneralLedgerPostingDocumentRuleBase implements
0117: AddAccountingLineRule<AccountingDocument>,
0118: GenerateGeneralLedgerPendingEntriesRule<AccountingDocument>,
0119: DeleteAccountingLineRule<AccountingDocument>,
0120: UpdateAccountingLineRule<AccountingDocument>,
0121: ReviewAccountingLineRule<AccountingDocument>,
0122: SufficientFundsCheckingPreparationRule,
0123: AccountingDocumentRuleBaseConstants {
0124: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0125: .getLogger(PurapAccountingDocumentRuleBase.class);
0126: private ParameterService parameterService;
0127:
0128: protected ParameterService getParameterService() {
0129: if (parameterService == null) {
0130: parameterService = SpringContext
0131: .getBean(ParameterService.class);
0132: }
0133: return parameterService;
0134: }
0135:
0136: /**
0137: * Indicates what is being done to an accounting line. This allows the same method to be used for different actions.
0138: */
0139: public enum AccountingLineAction {
0140: ADD(ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD), DELETE(
0141: ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE), UPDATE(
0142: ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE);
0143:
0144: public final String accessibilityErrorKey;
0145:
0146: AccountingLineAction(String accessabilityErrorKey) {
0147: this .accessibilityErrorKey = accessabilityErrorKey;
0148: }
0149: }
0150:
0151: // Inherited Document Specific Business Rules
0152: /**
0153: * This method performs common validation for Transactional Document routes. Note the rule framework will handle validating all
0154: * of the accounting lines and also those checks that would normally be done on a save, automatically for us.
0155: *
0156: * @param document
0157: * @return boolean True if the document is valid for routing, false otherwise.
0158: */
0159: @Override
0160: protected boolean processCustomRouteDocumentBusinessRules(
0161: Document document) {
0162: LOG
0163: .debug("processCustomRouteDocumentBusinessRules(Document) - start");
0164:
0165: boolean valid = true;
0166:
0167: AccountingDocument financialDocument = (AccountingDocument) document;
0168:
0169: // check to make sure the required number of accounting lines were met
0170: valid &= isAccountingLinesRequiredNumberForRoutingMet(financialDocument);
0171:
0172: // check balance
0173: valid &= isDocumentBalanceValid(financialDocument);
0174:
0175: LOG
0176: .debug("processCustomRouteDocumentBusinessRules(Document) - end");
0177: return valid;
0178: }
0179:
0180: /**
0181: * This method performs common validation for Transactional Document approvals. Note the rule framework will handle validating
0182: * all of the accounting lines and also those checks that would normally be done on an approval, automatically for us.
0183: *
0184: * @param approveEvent
0185: * @return boolean True if the document is valid for approval, false otherwise.
0186: */
0187: @Override
0188: protected boolean processCustomApproveDocumentBusinessRules(
0189: ApproveDocumentEvent approveEvent) {
0190: LOG
0191: .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - start");
0192:
0193: boolean valid = true;
0194:
0195: // allow accountingLine totals to change for BlanketApproveDocumentEvents, and only
0196: // for BlanketApproveDocumentEvents
0197: if (!(approveEvent instanceof BlanketApproveDocumentEvent)) {
0198: valid &= isAccountingLineTotalsUnchanged((AccountingDocument) approveEvent
0199: .getDocument());
0200: }
0201:
0202: LOG
0203: .debug("processCustomApproveDocumentBusinessRules(ApproveDocumentEvent) - end");
0204: return valid;
0205: }
0206:
0207: // Rule interface specific methods
0208: /**
0209: * This method performs common validation for adding of accounting lines. Then calls a custom method for more specific
0210: * validation.
0211: *
0212: * @see org.kuali.core.rule.AddAccountingLineRule#processAddAccountingLineBusinessRules(org.kuali.core.document.AccountingDocument,
0213: * org.kuali.core.bo.AccountingLine)
0214: */
0215: public boolean processAddAccountingLineBusinessRules(
0216: AccountingDocument financialDocument,
0217: AccountingLine accountingLine) {
0218: LOG
0219: .debug("processAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0220:
0221: boolean valid = checkAccountingLine(financialDocument,
0222: accountingLine);
0223: if (valid) {
0224: valid &= checkAccountingLineAccountAccessibility(
0225: financialDocument, accountingLine,
0226: AccountingLineAction.ADD);
0227: }
0228: if (valid) {
0229: valid &= processCustomAddAccountingLineBusinessRules(
0230: financialDocument, accountingLine);
0231: }
0232:
0233: LOG
0234: .debug("processAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0235: return valid;
0236: }
0237:
0238: /**
0239: * This method should be overridden in the children classes to implement business rules that don't fit into any of the other
0240: * AddAccountingLineRule interface methods.
0241: *
0242: * @param financialDocument
0243: * @param accountingLine
0244: * @return boolean
0245: */
0246: protected boolean processCustomAddAccountingLineBusinessRules(
0247: AccountingDocument financialDocument,
0248: AccountingLine accountingLine) {
0249: LOG
0250: .debug("processCustomAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0251:
0252: LOG
0253: .debug("processCustomAddAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0254: return true;
0255: }
0256:
0257: /**
0258: * This method performs common validation for deleting of accounting lines. Then calls a custom method for more specific
0259: * validation.
0260: *
0261: * @see org.kuali.core.rule.DeleteAccountingLineRule#processDeleteAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0262: * org.kuali.core.bo.AccountingLine, boolean)
0263: */
0264: public boolean processDeleteAccountingLineBusinessRules(
0265: AccountingDocument financialDocument,
0266: AccountingLine accountingLine,
0267: boolean lineWasAlreadyDeletedFromDocument) {
0268: LOG
0269: .debug("processDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - start");
0270:
0271: return verifyExistenceOfOtherAccessibleAccountingLines(
0272: financialDocument, lineWasAlreadyDeletedFromDocument);
0273: }
0274:
0275: /**
0276: * This method should be overridden in the children classes to implement deleteAccountingLine checks that don't fit into any of
0277: * the other DeleteAccountingLineRule interface methods.
0278: *
0279: * @param financialDocument
0280: * @param accountingLine
0281: * @param lineWasAlreadyDeletedFromDocument
0282: * @return boolean
0283: */
0284: protected boolean processCustomDeleteAccountingLineBusinessRules(
0285: AccountingDocument financialDocument,
0286: AccountingLine accountingLine,
0287: boolean lineWasAlreadyDeletedFromDocument) {
0288: LOG
0289: .debug("processCustomDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - start");
0290:
0291: LOG
0292: .debug("processCustomDeleteAccountingLineBusinessRules(AccountingDocument, AccountingLine, boolean) - end");
0293: return true;
0294: }
0295:
0296: /**
0297: * This method verifies that other lines exist on the document that this user has access to.
0298: *
0299: * @param financialDocument
0300: * @param lineWasAlreadyDeletedFromDocument
0301: * @return boolean
0302: */
0303: private boolean verifyExistenceOfOtherAccessibleAccountingLines(
0304: AccountingDocument financialDocument,
0305: boolean lineWasAlreadyDeletedFromDocument) {
0306: LOG
0307: .debug("verifyExistenceOfOtherAccessibleAccountingLines(AccountingDocument, boolean) - start");
0308:
0309: // verify that other accountingLines will exist after the deletion which are accessible to this user
0310: int minimumRemainingAccessibleLines = 1 + (lineWasAlreadyDeletedFromDocument ? 0
0311: : 1);
0312: boolean sufficientLines = hasAccessibleAccountingLines(
0313: financialDocument, minimumRemainingAccessibleLines);
0314: if (!sufficientLines) {
0315: GlobalVariables.getErrorMap().putError(
0316: ACCOUNTING_LINE_ERRORS,
0317: ERROR_ACCOUNTINGLINE_LASTACCESSIBLE_DELETE);
0318: }
0319:
0320: LOG
0321: .debug("verifyExistenceOfOtherAccessibleAccountingLines(AccountingDocument, boolean) - end");
0322: return sufficientLines;
0323: }
0324:
0325: /**
0326: * This method performs common validation for update of accounting lines. Then calls a custom method for more specific
0327: * validation.
0328: *
0329: * @see org.kuali.core.rule.UpdateAccountingLineRule#processUpdateAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0330: * org.kuali.core.bo.AccountingLine, org.kuali.core.bo.AccountingLine)
0331: */
0332: public boolean processUpdateAccountingLineBusinessRules(
0333: AccountingDocument financialDocument,
0334: AccountingLine accountingLine,
0335: AccountingLine updatedAccountingLine) {
0336: LOG
0337: .debug("processUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - start");
0338:
0339: boolean valid = checkAccountingLine(financialDocument,
0340: updatedAccountingLine);
0341: if (valid) {
0342: valid &= checkAccountingLineAccountAccessibility(
0343: financialDocument, updatedAccountingLine,
0344: AccountingLineAction.UPDATE);
0345: }
0346: if (valid) {
0347: valid &= processCustomUpdateAccountingLineBusinessRules(
0348: financialDocument, accountingLine,
0349: updatedAccountingLine);
0350: }
0351:
0352: LOG
0353: .debug("processUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - end");
0354: return valid;
0355: }
0356:
0357: /**
0358: * This method should be overridden in the children classes to implement updateAccountingLine checks that don't fit into any of
0359: * the other UpdateAccountingLineRule interface methods.
0360: *
0361: * @param accountingDocument
0362: * @param originalAccountingLine
0363: * @param updatedAccountingLine
0364: * @return boolean
0365: */
0366: protected boolean processCustomUpdateAccountingLineBusinessRules(
0367: AccountingDocument accountingDocument,
0368: AccountingLine originalAccountingLine,
0369: AccountingLine updatedAccountingLine) {
0370: LOG
0371: .debug("processCustomUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - start");
0372:
0373: LOG
0374: .debug("processCustomUpdateAccountingLineBusinessRules(AccountingDocument, AccountingLine, AccountingLine) - end");
0375: return true;
0376: }
0377:
0378: /**
0379: * Wrapper around global errorMap.put call, to allow better logging
0380: *
0381: * @param propertyName
0382: * @param errorKey
0383: * @param errorParams
0384: */
0385: protected void reportError(String propertyName, String errorKey,
0386: String... errorParams) {
0387: LOG.debug("reportError(String, String, String) - start");
0388:
0389: GlobalVariables.getErrorMap().putError(propertyName, errorKey,
0390: errorParams);
0391: if (LOG.isDebugEnabled()) {
0392: LOG.debug("rule failure at "
0393: + ExceptionUtils.describeStackLevels(1, 2));
0394: }
0395: }
0396:
0397: /**
0398: * Adds a global error for a missing required property. This is used for properties, such as reference origin code, which cannot
0399: * be required by the DataDictionary validation because not all documents require them.
0400: *
0401: * @param boe
0402: * @param propertyName
0403: */
0404: public static void putRequiredPropertyError(
0405: BusinessObjectEntry boe, String propertyName) {
0406: LOG
0407: .debug("putRequiredPropertyError(BusinessObjectEntry, String) - start");
0408:
0409: String label = boe.getAttributeDefinition(propertyName)
0410: .getShortLabel();
0411: GlobalVariables.getErrorMap().putError(propertyName,
0412: KFSKeyConstants.ERROR_REQUIRED, label);
0413:
0414: LOG
0415: .debug("putRequiredPropertyError(BusinessObjectEntry, String) - end");
0416: }
0417:
0418: /**
0419: * If the given accountingLine has an account which is inaccessible to the current user, an error message will be put into the
0420: * global ErrorMap and into the logfile.
0421: *
0422: * @param financialDocument
0423: * @param accountingLine
0424: * @param action
0425: * @return true if the given accountingLine refers to an account which allows it to be added, deleted, or updated
0426: */
0427: protected boolean checkAccountingLineAccountAccessibility(
0428: AccountingDocument financialDocument,
0429: AccountingLine accountingLine, AccountingLineAction action) {
0430: LOG
0431: .debug("checkAccountingLineAccountAccessibility(AccountingDocument, AccountingLine, AccountingLineAction) - start");
0432:
0433: boolean isAccessible = accountIsAccessible(financialDocument,
0434: accountingLine);
0435:
0436: // report (and log) errors
0437: if (!isAccessible) {
0438: String[] errorParams = new String[] {
0439: accountingLine.getAccountNumber(),
0440: GlobalVariables.getUserSession().getUniversalUser()
0441: .getPersonUserIdentifier() };
0442: GlobalVariables.getErrorMap().putError(
0443: KFSPropertyConstants.ACCOUNT_NUMBER,
0444: action.accessibilityErrorKey, errorParams);
0445:
0446: LOG.info("accountIsAccessible check failed: account "
0447: + errorParams[0] + ", user " + errorParams[1]);
0448: }
0449:
0450: LOG
0451: .debug("checkAccountingLineAccountAccessibility(AccountingDocument, AccountingLine, AccountingLineAction) - end");
0452: return isAccessible;
0453: }
0454:
0455: /**
0456: * @param financialDocument
0457: * @param accountingLine
0458: * @return true if the given accountingLine refers to an account which allows it to be added, deleted, or updated
0459: */
0460: protected boolean accountIsAccessible(
0461: AccountingDocument financialDocument,
0462: AccountingLine accountingLine) {
0463: LOG
0464: .debug("accountIsAccessible(AccountingDocument, AccountingLine) - start");
0465:
0466: boolean isAccessible = false;
0467:
0468: KualiWorkflowDocument workflowDocument = financialDocument
0469: .getDocumentHeader().getWorkflowDocument();
0470: ChartUser currentUser = (ChartUser) GlobalVariables
0471: .getUserSession().getUniversalUser().getModuleUser(
0472: ChartUser.MODULE_ID);
0473:
0474: if (workflowDocument.stateIsInitiated()
0475: || workflowDocument.stateIsSaved()) {
0476: isAccessible = true;
0477: } else {
0478: if (workflowDocument.stateIsEnroute()) {
0479: String chartCode = accountingLine
0480: .getChartOfAccountsCode();
0481: String accountNumber = accountingLine
0482: .getAccountNumber();
0483:
0484: // if a document is enroute, user can only refer to for accounts for which they are responsible
0485: isAccessible = currentUser.isResponsibleForAccount(
0486: chartCode, accountNumber);
0487: } else {
0488: if (workflowDocument.stateIsApproved()
0489: || workflowDocument.stateIsFinal()
0490: || workflowDocument.stateIsDisapproved()) {
0491: isAccessible = false;
0492: } else {
0493: if (workflowDocument.stateIsException()
0494: && currentUser.getUniversalUser()
0495: .isWorkflowExceptionUser()) {
0496: isAccessible = true;
0497: }
0498: }
0499: }
0500: }
0501:
0502: LOG
0503: .debug("accountIsAccessible(AccountingDocument, AccountingLine) - end");
0504: return isAccessible;
0505: }
0506:
0507: /**
0508: * @param financialDocument
0509: * @param min
0510: * @return true if the document has n (or more) accessible accountingLines
0511: */
0512: protected boolean hasAccessibleAccountingLines(
0513: AccountingDocument financialDocument, int min) {
0514: LOG
0515: .debug("hasAccessibleAccountingLines(AccountingDocument, int) - start");
0516:
0517: boolean hasLines = false;
0518:
0519: // only count if the doc is enroute
0520: KualiWorkflowDocument workflowDocument = financialDocument
0521: .getDocumentHeader().getWorkflowDocument();
0522: ChartUser currentUser = (ChartUser) GlobalVariables
0523: .getUserSession().getUniversalUser().getModuleUser(
0524: ChartUser.MODULE_ID);
0525: if (workflowDocument.stateIsEnroute()) {
0526: int accessibleLines = 0;
0527: for (Iterator i = financialDocument
0528: .getSourceAccountingLines().iterator(); (accessibleLines < min)
0529: && i.hasNext();) {
0530: AccountingLine line = (AccountingLine) i.next();
0531: if (accountIsAccessible(financialDocument, line)) {
0532: ++accessibleLines;
0533: }
0534: }
0535: for (Iterator i = financialDocument
0536: .getTargetAccountingLines().iterator(); (accessibleLines < min)
0537: && i.hasNext();) {
0538: AccountingLine line = (AccountingLine) i.next();
0539: if (accountIsAccessible(financialDocument, line)) {
0540: ++accessibleLines;
0541: }
0542: }
0543:
0544: hasLines = (accessibleLines >= min);
0545: } else {
0546: if (workflowDocument.stateIsException()
0547: && currentUser.getUniversalUser()
0548: .isWorkflowExceptionUser()) {
0549: hasLines = true;
0550: } else {
0551: if (workflowDocument.stateIsInitiated()
0552: || workflowDocument.stateIsSaved()) {
0553: hasLines = true;
0554: } else {
0555: hasLines = false;
0556: }
0557: }
0558: }
0559:
0560: LOG
0561: .debug("hasAccessibleAccountingLines(AccountingDocument, int) - end");
0562: return hasLines;
0563: }
0564:
0565: /**
0566: * This method performs common validation for review of accounting lines. Then calls a custom method for more specific
0567: * validation.
0568: *
0569: * @see org.kuali.core.rule.ReviewAccountingLineRule#processReviewAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0570: * org.kuali.core.bo.AccountingLine)
0571: */
0572: public boolean processReviewAccountingLineBusinessRules(
0573: AccountingDocument financialDocument,
0574: AccountingLine accountingLine) {
0575: LOG
0576: .debug("processReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0577:
0578: boolean valid = checkAccountingLine(financialDocument,
0579: accountingLine);
0580: if (valid) {
0581: valid &= processCustomReviewAccountingLineBusinessRules(
0582: financialDocument, accountingLine);
0583: }
0584:
0585: LOG
0586: .debug("processReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0587: return valid;
0588: }
0589:
0590: /**
0591: * This method should be overridden in the child classes to implement business rules that don't fit into any of the other
0592: * ReviewAccountingLineRule interface methods.
0593: *
0594: * @param financialDocument
0595: * @param accountingLine
0596: * @return boolean
0597: */
0598: protected boolean processCustomReviewAccountingLineBusinessRules(
0599: AccountingDocument financialDocument,
0600: AccountingLine accountingLine) {
0601: LOG
0602: .debug("processCustomReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - start");
0603:
0604: LOG
0605: .debug("processCustomReviewAccountingLineBusinessRules(AccountingDocument, AccountingLine) - end");
0606: return true;
0607: }
0608:
0609: /**
0610: * This contains business rule checks that are common to all accounting line events for all Transaction Processing Financial
0611: * eDocs. Note existence, requirement, and format checking are not done in this method, because those checks are handled
0612: * automatically by the data dictionary validation framework. This method is responsible for call validate methods that check
0613: * the activity of an instance.
0614: *
0615: * @param accountingLine
0616: * @param financialDocument
0617: * @return true if no errors occurred
0618: */
0619: private final boolean checkAccountingLine(
0620: AccountingDocument financialDocument,
0621: AccountingLine accountingLine) {
0622: LOG.debug("entering processAccountingLine");
0623:
0624: boolean valid = true;
0625: int originalErrorCount = GlobalVariables.getErrorMap()
0626: .getErrorCount();
0627:
0628: // now make sure all the necessary business objects are fully populated
0629: accountingLine.refreshNonUpdateableReferences();
0630:
0631: // validate required checks in addition to format checks
0632: SpringContext.getBean(DictionaryValidationService.class)
0633: .validateBusinessObject(accountingLine);
0634:
0635: // check to see if any errors were reported
0636: int currentErrorCount = GlobalVariables.getErrorMap()
0637: .getErrorCount();
0638: valid &= (currentErrorCount == originalErrorCount);
0639:
0640: if (!valid) {
0641: // logic to replace generic amount error messages
0642: // create a list of accounting line attribute keys
0643: ArrayList linePatterns = new ArrayList();
0644: // source patterns: removing wildcards
0645: linePatterns.addAll(Arrays.asList(StringUtils.replace(
0646: SOURCE_ACCOUNTING_LINE_ERROR_PATTERN, "*", "")
0647: .split(",")));
0648: // target patterns: removing wildcards
0649: linePatterns.addAll(Arrays.asList(StringUtils.replace(
0650: TARGET_ACCOUNTING_LINE_ERROR_PATTERN, "*", "")
0651: .split(",")));
0652:
0653: // see if any lines have errors
0654: for (Iterator i = GlobalVariables.getErrorMap()
0655: .getPropertiesWithErrors().iterator(); i.hasNext();) {
0656: String property = (String) i.next();
0657: // only concerned about amount field errors
0658: if (property.endsWith("." + AMOUNT_PROPERTY_NAME)) {
0659: // check if the amount field is associated with an accounting line
0660: boolean isLineProperty = true;
0661: for (Iterator linePatternsIterator = linePatterns
0662: .iterator(); i.hasNext() && !isLineProperty;) {
0663: isLineProperty = property
0664: .startsWith((String) linePatternsIterator
0665: .next());
0666: }
0667: if (isLineProperty) {
0668: // find the specific error messages for the property
0669: for (ListIterator errors = GlobalVariables
0670: .getErrorMap().getMessages(property)
0671: .listIterator(); errors.hasNext();) {
0672: ErrorMessage error = (ErrorMessage) errors
0673: .next();
0674: String errorKey = null;
0675: String[] params = new String[2];
0676: if (StringUtils.equals(
0677: ERROR_INVALID_FORMAT, error
0678: .getErrorKey())) {
0679: errorKey = ERROR_DOCUMENT_ACCOUNTING_LINE_INVALID_FORMAT;
0680: params[1] = accountingLine.getAmount()
0681: .toString();
0682: } else {
0683: if (StringUtils.equals(
0684: ERROR_MAX_LENGTH, error
0685: .getErrorKey())) {
0686: errorKey = ERROR_DOCUMENT_ACCOUNTING_LINE_MAX_LENGTH;
0687:
0688: // String value = ObjectUtils.getPropertyValue(accountingLine,
0689: // KFSConstants.AMOUNT_PROPERTY_NAME)
0690:
0691: }
0692: }
0693: if (errorKey != null) {
0694:
0695: LOG.debug("replacing: " + error);
0696: // now replace error message
0697: error.setErrorKey(errorKey);
0698: // replace parameters
0699: params[0] = SpringContext.getBean(
0700: DataDictionaryService.class)
0701: .getAttributeLabel(
0702: accountingLine
0703: .getClass(),
0704: AMOUNT_PROPERTY_NAME);
0705: error.setMessageParameters(params);
0706: // put back where it came form
0707: errors.set(error);
0708: LOG.debug("with: " + error);
0709: }
0710: }
0711: }
0712: }
0713: }
0714: } else { // continue on with the rest of the validation if the accounting line contains valid values
0715: // Check the amount entered
0716: valid &= isAmountValid(financialDocument, accountingLine);
0717:
0718: // Perform the standard accounting line rule checking - checks activity
0719: // of each attribute in addition to existence
0720: // NOTE: this AccountingLineRuleUtil should be spring managed so that we could swap this out per project
0721: valid &= PurapAccountingLineRuleUtil
0722: .validateAccountingLine(
0723: accountingLine,
0724: SpringContext
0725: .getBean(DataDictionaryService.class));
0726:
0727: if (valid) { // the following checks assume existence, so if the above method failed, we don't want to call these
0728: Class documentClass = getAccountingLineDocumentClass(financialDocument);
0729:
0730: // Check the object code to see if it's restricted or not
0731: valid &= isObjectCodeAllowed(documentClass,
0732: accountingLine);
0733:
0734: // Check the object code type allowances
0735: valid &= isObjectTypeAllowed(documentClass,
0736: accountingLine);
0737:
0738: // Check the object sub-type code allowances
0739: valid &= isObjectSubTypeAllowed(documentClass,
0740: accountingLine);
0741:
0742: // Check the object level allowances
0743: valid &= isObjectLevelAllowed(documentClass,
0744: accountingLine);
0745:
0746: // Check the object consolidation allowances
0747: valid &= isObjectConsolidationAllowed(documentClass,
0748: accountingLine);
0749:
0750: // Check the sub fund group allowances
0751: valid &= isSubFundGroupAllowed(documentClass,
0752: accountingLine);
0753:
0754: // Check the fund group allowances
0755: valid &= isFundGroupAllowed(documentClass,
0756: accountingLine);
0757: }
0758: }
0759:
0760: if (!valid) {
0761: LOG
0762: .info("business rule checks failed in processAccountingLine in KualiRuleServiceImpl");
0763: }
0764:
0765: LOG.debug("leaving processAccountingLine");
0766:
0767: return valid;
0768: }
0769:
0770: /**
0771: * Perform business rules common to all transactional documents when generating general ledger pending entries.
0772: *
0773: * @see org.kuali.core.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.core.document.AccountingDocument,
0774: * org.kuali.core.bo.AccountingLine, org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper)
0775: */
0776: public boolean processGenerateGeneralLedgerPendingEntries(
0777: AccountingDocument accountingDocument,
0778: AccountingLine accountingLine,
0779: GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
0780: LOG
0781: .debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - start");
0782:
0783: // handle the explicit entry
0784: // create a reference to the explicitEntry to be populated, so we can pass to the offset method later
0785: boolean success = true;
0786: GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();
0787: success &= processExplicitGeneralLedgerPendingEntry(
0788: accountingDocument, sequenceHelper, accountingLine,
0789: explicitEntry);
0790:
0791: // increment the sequence counter
0792: sequenceHelper.increment();
0793:
0794: // handle the offset entry
0795: GeneralLedgerPendingEntry offsetEntry = (GeneralLedgerPendingEntry) ObjectUtils
0796: .deepCopy(explicitEntry);
0797: success &= processOffsetGeneralLedgerPendingEntry(
0798: accountingDocument, sequenceHelper, accountingLine,
0799: explicitEntry, offsetEntry);
0800:
0801: // handle the situation where the document is an error correction or is corrected
0802: handleDocumentErrorCorrection(accountingDocument,
0803: accountingLine);
0804:
0805: LOG
0806: .debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - end");
0807: return success;
0808: }
0809:
0810: // Transactional Document Specific Rule Implementations
0811: /**
0812: * This method processes all necessary information to build an explicit general ledger entry, and then adds that to the
0813: * document.
0814: *
0815: * @param accountingDocument
0816: * @param sequenceHelper
0817: * @param accountingLine
0818: * @param explicitEntry
0819: * @return boolean True if the explicit entry generation was successful, false otherwise.
0820: */
0821: protected boolean processExplicitGeneralLedgerPendingEntry(
0822: AccountingDocument accountingDocument,
0823: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0824: AccountingLine accountingLine,
0825: GeneralLedgerPendingEntry explicitEntry) {
0826: LOG
0827: .debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - start");
0828:
0829: // populate the explicit entry
0830: populateExplicitGeneralLedgerPendingEntry(accountingDocument,
0831: accountingLine, sequenceHelper, explicitEntry);
0832:
0833: // hook for children documents to implement document specific GLPE field mappings
0834: customizeExplicitGeneralLedgerPendingEntry(accountingDocument,
0835: accountingLine, explicitEntry);
0836:
0837: // add the new explicit entry to the document now
0838: accountingDocument.getGeneralLedgerPendingEntries().add(
0839: explicitEntry);
0840:
0841: LOG
0842: .debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - end");
0843: return true;
0844: }
0845:
0846: /**
0847: * This method processes an accounting line's information to build an offset entry, and then adds that to the document.
0848: *
0849: * @param accountingDocument
0850: * @param sequenceHelper
0851: * @param accountingLine
0852: * @param explicitEntry
0853: * @param offsetEntry
0854: * @return boolean True if the offset generation is successful.
0855: */
0856: protected boolean processOffsetGeneralLedgerPendingEntry(
0857: AccountingDocument accountingDocument,
0858: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0859: AccountingLine accountingLine,
0860: GeneralLedgerPendingEntry explicitEntry,
0861: GeneralLedgerPendingEntry offsetEntry) {
0862: LOG
0863: .debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
0864:
0865: boolean success = true;
0866: // populate the offset entry
0867: success &= populateOffsetGeneralLedgerPendingEntry(
0868: accountingDocument.getPostingYear(), explicitEntry,
0869: sequenceHelper, offsetEntry);
0870:
0871: // hook for children documents to implement document specific field mappings for the GLPE
0872: success &= customizeOffsetGeneralLedgerPendingEntry(
0873: accountingDocument, accountingLine, explicitEntry,
0874: offsetEntry);
0875:
0876: // add the new offset entry to the document now
0877: accountingDocument.getGeneralLedgerPendingEntries().add(
0878: offsetEntry);
0879:
0880: LOG
0881: .debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
0882: return success;
0883: }
0884:
0885: /**
0886: * This method can be overridden to set attributes on the explicit entry in a way specific to a particular document. By default
0887: * the explicit entry is returned without modification.
0888: *
0889: * @param accountingDocument
0890: * @param accountingLine
0891: * @param explicitEntry
0892: */
0893: protected void customizeExplicitGeneralLedgerPendingEntry(
0894: AccountingDocument accountingDocument,
0895: AccountingLine accountingLine,
0896: GeneralLedgerPendingEntry explicitEntry) {
0897: }
0898:
0899: /**
0900: * This method can be overridden to set attributes on the offset entry in a way specific to a particular document. By default
0901: * the offset entry is not modified.
0902: *
0903: * @param accountingDocument
0904: * @param accountingLine
0905: * @param explicitEntry
0906: * @param offsetEntry
0907: * @return whether the offset generation is successful
0908: */
0909: protected boolean customizeOffsetGeneralLedgerPendingEntry(
0910: AccountingDocument accountingDocument,
0911: AccountingLine accountingLine,
0912: GeneralLedgerPendingEntry explicitEntry,
0913: GeneralLedgerPendingEntry offsetEntry) {
0914: LOG
0915: .debug("customizeOffsetGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
0916:
0917: LOG
0918: .debug("customizeOffsetGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
0919: return true;
0920: }
0921:
0922: /**
0923: * Checks accounting line totals for approval to make sure that they have not changed.
0924: *
0925: * @param accountingDocument
0926: * @return boolean True if the number of accounting lines are valid for routing, false otherwise.
0927: */
0928: protected boolean isAccountingLineTotalsUnchanged(
0929: AccountingDocument accountingDocument) {
0930: LOG
0931: .debug("isAccountingLineTotalsUnchanged(AccountingDocument) - start");
0932:
0933: AccountingDocument persistedDocument = null;
0934:
0935: persistedDocument = retrievePersistedDocument(accountingDocument);
0936:
0937: boolean isUnchanged = true;
0938: if (persistedDocument == null) {
0939: handleNonExistentDocumentWhenApproving(accountingDocument);
0940: } else {
0941: // retrieve the persisted totals
0942: KualiDecimal persistedSourceLineTotal = persistedDocument
0943: .getSourceTotal();
0944: KualiDecimal persistedTargetLineTotal = persistedDocument
0945: .getTargetTotal();
0946:
0947: // retrieve the updated totals
0948: KualiDecimal currentSourceLineTotal = accountingDocument
0949: .getSourceTotal();
0950: KualiDecimal currentTargetLineTotal = accountingDocument
0951: .getTargetTotal();
0952:
0953: // make sure that totals have remained unchanged, if not, recognize that, and
0954: // generate appropriate error messages
0955: if (currentSourceLineTotal
0956: .compareTo(persistedSourceLineTotal) != 0) {
0957: isUnchanged = false;
0958:
0959: // build out error message
0960: buildTotalChangeErrorMessage(
0961: SOURCE_ACCOUNTING_LINE_ERRORS,
0962: persistedSourceLineTotal,
0963: currentSourceLineTotal);
0964: }
0965:
0966: if (currentTargetLineTotal
0967: .compareTo(persistedTargetLineTotal) != 0) {
0968: isUnchanged = false;
0969:
0970: // build out error message
0971: buildTotalChangeErrorMessage(
0972: TARGET_ACCOUNTING_LINE_ERRORS,
0973: persistedTargetLineTotal,
0974: currentTargetLineTotal);
0975: }
0976: }
0977:
0978: LOG
0979: .debug("isAccountingLineTotalsUnchanged(AccountingDocument) - end");
0980: return isUnchanged;
0981: }
0982:
0983: /**
0984: * attempt to retrieve the document from the DB for comparison
0985: *
0986: * @param accountingDocument
0987: * @return AccountingDocument
0988: */
0989: protected AccountingDocument retrievePersistedDocument(
0990: AccountingDocument accountingDocument) {
0991: LOG
0992: .debug("retrievePersistedDocument(AccountingDocument) - start");
0993:
0994: AccountingDocument persistedDocument = null;
0995:
0996: try {
0997: persistedDocument = (AccountingDocument) SpringContext
0998: .getBean(DocumentService.class)
0999: .getByDocumentHeaderId(
1000: accountingDocument.getDocumentNumber());
1001: } catch (WorkflowException we) {
1002: LOG.error("retrievePersistedDocument(AccountingDocument)",
1003: we);
1004:
1005: handleNonExistentDocumentWhenApproving(accountingDocument);
1006: }
1007:
1008: LOG
1009: .debug("retrievePersistedDocument(AccountingDocument) - end");
1010: return persistedDocument;
1011: }
1012:
1013: /**
1014: * This method builds out the error message for when totals have changed.
1015: *
1016: * @param propertyName
1017: * @param persistedSourceLineTotal
1018: * @param currentSourceLineTotal
1019: */
1020: protected void buildTotalChangeErrorMessage(String propertyName,
1021: KualiDecimal persistedSourceLineTotal,
1022: KualiDecimal currentSourceLineTotal) {
1023: LOG
1024: .debug("buildTotalChangeErrorMessage(String, KualiDecimal, KualiDecimal) - start");
1025:
1026: String persistedTotal = (String) new CurrencyFormatter()
1027: .format(persistedSourceLineTotal);
1028: String currentTotal = (String) new CurrencyFormatter()
1029: .format(currentSourceLineTotal);
1030: GlobalVariables
1031: .getErrorMap()
1032: .putError(
1033: propertyName,
1034: ERROR_DOCUMENT_SINGLE_ACCOUNTING_LINE_SECTION_TOTAL_CHANGED,
1035: new String[] { persistedTotal, currentTotal });
1036:
1037: LOG
1038: .debug("buildTotalChangeErrorMessage(String, KualiDecimal, KualiDecimal) - end");
1039: }
1040:
1041: /**
1042: * Handles the case when a non existent document is attempted to be retrieve and that if it's in an initiated state, it's ok.
1043: *
1044: * @param accountingDocument
1045: */
1046: protected final void handleNonExistentDocumentWhenApproving(
1047: AccountingDocument accountingDocument) {
1048: LOG
1049: .debug("handleNonExistentDocumentWhenApproving(AccountingDocument) - start");
1050:
1051: // check to make sure this isn't an initiated document being blanket approved
1052: if (!accountingDocument.getDocumentHeader()
1053: .getWorkflowDocument().stateIsInitiated()) {
1054: throw new IllegalStateException(
1055: "Document "
1056: + accountingDocument.getDocumentNumber()
1057: + " is not a valid document that currently exists in the system.");
1058: }
1059:
1060: LOG
1061: .debug("handleNonExistentDocumentWhenApproving(AccountingDocument) - end");
1062: }
1063:
1064: /**
1065: * Checks accounting line number limits for routing. This method is for overriding by documents with rules about the total
1066: * number of lines in both sections combined.
1067: *
1068: * @param accountingDocument
1069: * @return boolean True if the number of accounting lines are valid for routing, false otherwise.
1070: */
1071: protected boolean isAccountingLinesRequiredNumberForRoutingMet(
1072: AccountingDocument accountingDocument) {
1073: LOG
1074: .debug("isAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1075:
1076: boolean met = true;
1077: met &= isSourceAccountingLinesRequiredNumberForRoutingMet(accountingDocument);
1078: met &= isTargetAccountingLinesRequiredNumberForRoutingMet(accountingDocument);
1079:
1080: LOG
1081: .debug("isAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1082: return met;
1083: }
1084:
1085: /**
1086: * Some double-sided documents also allow for one sided entries for correcting - so if one side is empty, the other side must
1087: * have at least two lines in it. The balancing rules take care of validation of amounts.
1088: *
1089: * @param accountingDocument
1090: * @return boolean
1091: */
1092: protected boolean isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(
1093: AccountingDocument accountingDocument) {
1094: LOG
1095: .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1096:
1097: int sourceSectionSize = accountingDocument
1098: .getSourceAccountingLines().size();
1099: int targetSectionSize = accountingDocument
1100: .getTargetAccountingLines().size();
1101:
1102: if ((sourceSectionSize == 0 && targetSectionSize < 2)
1103: || (targetSectionSize == 0 && sourceSectionSize < 2)) {
1104: GlobalVariables
1105: .getErrorMap()
1106: .putError(
1107: ACCOUNTING_LINE_ERRORS,
1108: ERROR_DOCUMENT_OPTIONAL_ONE_SIDED_DOCUMENT_REQUIRED_NUMBER_OF_ACCOUNTING_LINES_NOT_MET);
1109:
1110: LOG
1111: .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1112: return false;
1113: }
1114:
1115: LOG
1116: .debug("isOptionalOneSidedDocumentAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1117: return true;
1118: }
1119:
1120: /**
1121: * This method checks the amount of a given accounting line to make sure it's not 0, it's positive for regular documents, and
1122: * negative for correction documents.
1123: *
1124: * @param document
1125: * @param accountingLine
1126: * @return boolean True if there aren't any issues, false otherwise.
1127: */
1128: public boolean isAmountValid(AccountingDocument document,
1129: AccountingLine accountingLine) {
1130: LOG
1131: .debug("isAmountValid(AccountingDocument, AccountingLine) - start");
1132:
1133: KualiDecimal amount = accountingLine.getAmount();
1134:
1135: // Check for zero amount, or negative on original (non-correction) document; no sign check for documents that are
1136: // corrections to previous documents
1137: String correctsDocumentId = document.getDocumentHeader()
1138: .getFinancialDocumentInErrorNumber();
1139: if (ZERO.compareTo(amount) == 0) { // amount == 0
1140: GlobalVariables.getErrorMap().putError(
1141: AMOUNT_PROPERTY_NAME, ERROR_ZERO_AMOUNT,
1142: "an accounting line");
1143: LOG.info("failing isAmountValid - zero check");
1144: return false;
1145: } else {
1146: if (null == correctsDocumentId
1147: && ZERO.compareTo(amount) == 1) { // amount < 0
1148: GlobalVariables.getErrorMap().putError(
1149: AMOUNT_PROPERTY_NAME,
1150: ERROR_INVALID_NEGATIVE_AMOUNT_NON_CORRECTION);
1151: LOG
1152: .info("failing isAmountValid - correctsDocumentId check && amount == 1");
1153: return false;
1154: }
1155: }
1156:
1157: return true;
1158: }
1159:
1160: /**
1161: * This method will check to make sure that the required number of target accounting lines for routing, exist in the document.
1162: * This method represents the default implementation, which is that at least one target accounting line must exist.
1163: *
1164: * @param accountingDocument
1165: * @return isOk
1166: */
1167: protected boolean isTargetAccountingLinesRequiredNumberForRoutingMet(
1168: AccountingDocument accountingDocument) {
1169: LOG
1170: .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1171:
1172: if (0 == accountingDocument.getTargetAccountingLines().size()) {
1173: GlobalVariables.getErrorMap().putError(
1174: TARGET_ACCOUNTING_LINE_ERRORS,
1175: ERROR_DOCUMENT_TARGET_SECTION_NO_ACCOUNTING_LINES,
1176: new String[] { accountingDocument
1177: .getTargetAccountingLinesSectionTitle() });
1178:
1179: LOG
1180: .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1181: return false;
1182: } else {
1183: LOG
1184: .debug("isTargetAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1185: return true;
1186: }
1187: }
1188:
1189: /**
1190: * This method will check to make sure that the required number of source accounting lines for routing, exist in the document.
1191: * This method represents the default implementation, which is that at least one source accounting line must exist.
1192: *
1193: * @param accountingDocument
1194: * @return isOk
1195: */
1196: protected boolean isSourceAccountingLinesRequiredNumberForRoutingMet(
1197: AccountingDocument accountingDocument) {
1198: LOG
1199: .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - start");
1200:
1201: if (0 == accountingDocument.getSourceAccountingLines().size()) {
1202: GlobalVariables.getErrorMap().putError(
1203: SOURCE_ACCOUNTING_LINE_ERRORS,
1204: ERROR_DOCUMENT_SOURCE_SECTION_NO_ACCOUNTING_LINES,
1205: new String[] { accountingDocument
1206: .getSourceAccountingLinesSectionTitle() });
1207:
1208: LOG
1209: .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1210: return false;
1211: } else {
1212: LOG
1213: .debug("isSourceAccountingLinesRequiredNumberForRoutingMet(AccountingDocument) - end");
1214: return true;
1215: }
1216: }
1217:
1218: /**
1219: * This is the default implementation for Transactional Documents, which sums the amounts of all of the Debit GLPEs, and
1220: * compares it to the sum of all of the Credit GLPEs. In general, this algorithm works, but it does not work for some specific
1221: * documents such as the Journal Voucher. The method name denotes not an expected behavior, but a more general title so that
1222: * some documents that don't use this default implementation, can override just this method without having to override the
1223: * calling method.
1224: *
1225: * @param accountingDocument
1226: * @return boolean True if the document is balanced, false otherwise.
1227: */
1228: protected boolean isDocumentBalanceValid(
1229: AccountingDocument accountingDocument) {
1230: LOG.debug("isDocumentBalanceValid(AccountingDocument) - start");
1231:
1232: boolean returnboolean = isDocumentBalanceValidConsideringDebitsAndCredits(accountingDocument);
1233: LOG.debug("isDocumentBalanceValid(AccountingDocument) - end");
1234: return returnboolean;
1235: }
1236:
1237: /**
1238: * This method sums all of the debit GLPEs up and sums all of the credit GLPEs up and then compares the totals to each other,
1239: * returning true if they are equal and false if they are not.
1240: *
1241: * @param accountingDocument
1242: * @return boolean
1243: */
1244: protected boolean isDocumentBalanceValidConsideringDebitsAndCredits(
1245: AccountingDocument accountingDocument) {
1246: LOG
1247: .debug("isDocumentBalanceValidConsideringDebitsAndCredits(AccountingDocument) - start");
1248:
1249: // generate GLPEs specifically here so that we can compare debits to credits
1250: if (!SpringContext
1251: .getBean(GeneralLedgerPendingEntryService.class)
1252: .generateGeneralLedgerPendingEntries(accountingDocument)) {
1253: throw new ValidationException(
1254: "general ledger GLPE generation failed");
1255: }
1256:
1257: // now loop through all of the GLPEs and calculate buckets for debits and credits
1258: KualiDecimal creditAmount = new KualiDecimal(0);
1259: KualiDecimal debitAmount = new KualiDecimal(0);
1260: Iterator i = accountingDocument
1261: .getGeneralLedgerPendingEntries().iterator();
1262: while (i.hasNext()) {
1263: GeneralLedgerPendingEntry glpe = (GeneralLedgerPendingEntry) i
1264: .next();
1265: if (!glpe.isTransactionEntryOffsetIndicator()) { // make sure we are looking at only the explicit entries
1266: if (KFSConstants.GL_CREDIT_CODE.equals(glpe
1267: .getTransactionDebitCreditCode())) {
1268: creditAmount = creditAmount.add(glpe
1269: .getTransactionLedgerEntryAmount());
1270: } else { // DEBIT
1271: debitAmount = debitAmount.add(glpe
1272: .getTransactionLedgerEntryAmount());
1273: }
1274: }
1275: }
1276: boolean isValid = debitAmount.compareTo(creditAmount) == 0;
1277:
1278: if (!isValid) {
1279: GlobalVariables.getErrorMap().putError(
1280: ACCOUNTING_LINE_ERRORS, ERROR_DOCUMENT_BALANCE);
1281: }
1282:
1283: LOG
1284: .debug("isDocumentBalanceValidConsideringDebitsAndCredits(AccountingDocument) - end");
1285: return isValid;
1286: }
1287:
1288: // Other Helper Methods
1289: /**
1290: * This populates an empty GeneralLedgerPendingEntry explicitEntry object instance with default values.
1291: *
1292: * @param accountingDocument
1293: * @param accountingLine
1294: * @param sequenceHelper
1295: * @param explicitEntry
1296: */
1297: protected void populateExplicitGeneralLedgerPendingEntry(
1298: AccountingDocument accountingDocument,
1299: AccountingLine accountingLine,
1300: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
1301: GeneralLedgerPendingEntry explicitEntry) {
1302: LOG
1303: .debug("populateExplicitGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper, GeneralLedgerPendingEntry) - start");
1304:
1305: explicitEntry.setFinancialDocumentTypeCode(SpringContext
1306: .getBean(DocumentTypeService.class)
1307: .getDocumentTypeCodeByClass(
1308: accountingDocument.getClass()));
1309: explicitEntry.setVersionNumber(new Long(1));
1310: explicitEntry
1311: .setTransactionLedgerEntrySequenceNumber(new Integer(
1312: sequenceHelper.getSequenceCounter()));
1313: Timestamp transactionTimestamp = new Timestamp(SpringContext
1314: .getBean(DateTimeService.class).getCurrentDate()
1315: .getTime());
1316: explicitEntry.setTransactionDate(new java.sql.Date(
1317: transactionTimestamp.getTime()));
1318: explicitEntry.setTransactionEntryProcessedTs(new java.sql.Date(
1319: transactionTimestamp.getTime()));
1320: explicitEntry.setAccountNumber(accountingLine
1321: .getAccountNumber());
1322: if (accountingLine.getAccount().getAccountSufficientFundsCode() == null) {
1323: accountingLine.getAccount().setAccountSufficientFundsCode(
1324: KFSConstants.SF_TYPE_NO_CHECKING);
1325: }
1326: explicitEntry.setAcctSufficientFundsFinObjCd(SpringContext
1327: .getBean(SufficientFundsService.class)
1328: .getSufficientFundsObjectCode(
1329: accountingLine.getObjectCode(),
1330: accountingLine.getAccount()
1331: .getAccountSufficientFundsCode()));
1332: explicitEntry
1333: .setFinancialDocumentApprovedCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.NO);
1334: explicitEntry.setTransactionEncumbranceUpdateCode(BLANK_SPACE);
1335: explicitEntry.setFinancialBalanceTypeCode(BALANCE_TYPE_ACTUAL); // this is the default that most documents use
1336: explicitEntry.setChartOfAccountsCode(accountingLine
1337: .getChartOfAccountsCode());
1338: explicitEntry
1339: .setTransactionDebitCreditCode(isDebit(
1340: accountingDocument, accountingLine) ? KFSConstants.GL_DEBIT_CODE
1341: : KFSConstants.GL_CREDIT_CODE);
1342: explicitEntry
1343: .setFinancialSystemOriginationCode(SpringContext
1344: .getBean(HomeOriginationService.class)
1345: .getHomeOrigination()
1346: .getFinSystemHomeOriginationCode());
1347: explicitEntry.setDocumentNumber(accountingLine
1348: .getDocumentNumber());
1349: explicitEntry.setFinancialObjectCode(accountingLine
1350: .getFinancialObjectCode());
1351: ObjectCode objectCode = accountingLine.getObjectCode();
1352: if (ObjectUtils.isNull(objectCode)) {
1353: accountingLine.refreshReferenceObject("objectCode");
1354: }
1355: explicitEntry.setFinancialObjectTypeCode(accountingLine
1356: .getObjectCode().getFinancialObjectTypeCode());
1357: explicitEntry.setOrganizationDocumentNumber(accountingDocument
1358: .getDocumentHeader().getOrganizationDocumentNumber());
1359: explicitEntry.setOrganizationReferenceId(accountingLine
1360: .getOrganizationReferenceId());
1361: explicitEntry.setProjectCode(getEntryValue(accountingLine
1362: .getProjectCode(), GENERAL_LEDGER_PENDING_ENTRY_CODE
1363: .getBlankProjectCode()));
1364: explicitEntry
1365: .setReferenceFinancialDocumentNumber(getEntryValue(
1366: accountingLine.getReferenceNumber(),
1367: BLANK_SPACE));
1368: explicitEntry
1369: .setReferenceFinancialDocumentTypeCode(getEntryValue(
1370: accountingLine.getReferenceTypeCode(),
1371: BLANK_SPACE));
1372: explicitEntry
1373: .setReferenceFinancialSystemOriginationCode(getEntryValue(
1374: accountingLine.getReferenceOriginCode(),
1375: BLANK_SPACE));
1376: explicitEntry.setSubAccountNumber(getEntryValue(accountingLine
1377: .getSubAccountNumber(),
1378: GENERAL_LEDGER_PENDING_ENTRY_CODE
1379: .getBlankSubAccountNumber()));
1380: explicitEntry.setFinancialSubObjectCode(getEntryValue(
1381: accountingLine.getFinancialSubObjectCode(),
1382: GENERAL_LEDGER_PENDING_ENTRY_CODE
1383: .getBlankFinancialSubObjectCode()));
1384: explicitEntry.setTransactionEntryOffsetIndicator(false);
1385: explicitEntry
1386: .setTransactionLedgerEntryAmount(getGeneralLedgerPendingEntryAmountForAccountingLine(accountingLine));
1387: explicitEntry
1388: .setTransactionLedgerEntryDescription(getEntryValue(
1389: accountingLine
1390: .getFinancialDocumentLineDescription(),
1391: accountingDocument.getDocumentHeader()
1392: .getFinancialDocumentDescription()));
1393: explicitEntry.setUniversityFiscalPeriodCode(null); // null here, is assigned during batch or in specific document rule
1394: // classes
1395: explicitEntry.setUniversityFiscalYear(accountingDocument
1396: .getPostingYear());
1397:
1398: // explicitEntry.setBudgetYear(accountingLine.getBudgetYear());
1399: // explicitEntry.setBudgetYearFundingSourceCode(budgetYearFundingSourceCode);
1400:
1401: LOG
1402: .debug("populateExplicitGeneralLedgerPendingEntry(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper, GeneralLedgerPendingEntry) - end");
1403: }
1404:
1405: /**
1406: * This is responsible for properly negating the sign on an accounting line's amount when its associated document is an error
1407: * correction.
1408: *
1409: * @param accountingDocument
1410: * @param accountingLine
1411: */
1412: private final void handleDocumentErrorCorrection(
1413: AccountingDocument accountingDocument,
1414: AccountingLine accountingLine) {
1415: LOG
1416: .debug("handleDocumentErrorCorrection(AccountingDocument, AccountingLine) - start");
1417:
1418: // If the document corrects another document, make sure the accounting line has the correct sign.
1419: if ((null == accountingDocument.getDocumentHeader()
1420: .getFinancialDocumentInErrorNumber() && accountingLine
1421: .getAmount().isNegative())
1422: || (null != accountingDocument.getDocumentHeader()
1423: .getFinancialDocumentInErrorNumber() && accountingLine
1424: .getAmount().isPositive())) {
1425: accountingLine.setAmount(accountingLine.getAmount()
1426: .multiply(new KualiDecimal(1)));
1427: }
1428:
1429: LOG
1430: .debug("handleDocumentErrorCorrection(AccountingDocument, AccountingLine) - end");
1431: }
1432:
1433: /**
1434: * Determines whether an accounting line is an asset line.
1435: *
1436: * @param accountingLine
1437: * @return boolean True if a line is an asset line.
1438: */
1439: public final boolean isAsset(AccountingLine accountingLine) {
1440: LOG.debug("isAsset(AccountingLine) - start");
1441:
1442: boolean returnboolean = isAssetTypeCode(AccountingDocumentRuleUtil
1443: .getObjectCodeTypeCodeWithoutSideEffects(accountingLine));
1444: LOG.debug("isAsset(AccountingLine) - end");
1445: return returnboolean;
1446: }
1447:
1448: /**
1449: * Determines whether an accounting line is a liability line.
1450: *
1451: * @param accountingLine
1452: * @return boolean True if the line is a liability line.
1453: */
1454: public final boolean isLiability(AccountingLine accountingLine) {
1455: LOG.debug("isLiability(AccountingLine) - start");
1456:
1457: boolean returnboolean = isLiabilityTypeCode(AccountingDocumentRuleUtil
1458: .getObjectCodeTypeCodeWithoutSideEffects(accountingLine));
1459: LOG.debug("isLiability(AccountingLine) - end");
1460: return returnboolean;
1461: }
1462:
1463: /**
1464: * Determines whether an accounting line is an income line or not. This goes agains the configurable object type code list in
1465: * the ApplicationParameter mechanism. This list can be configured externally.
1466: *
1467: * @param accountingLine
1468: * @return boolean True if the line is an income line.
1469: */
1470: public final boolean isIncome(AccountingLine accountingLine) {
1471: LOG.debug("isIncome(AccountingLine) - start");
1472:
1473: boolean returnboolean = AccountingDocumentRuleUtil
1474: .isIncome(accountingLine);
1475: LOG.debug("isIncome(AccountingLine) - end");
1476: return returnboolean;
1477: }
1478:
1479: /**
1480: * Check object code type to determine whether the accounting line is expense.
1481: *
1482: * @param accountingLine
1483: * @return boolean True if the line is an expense line.
1484: */
1485: public boolean isExpense(AccountingLine accountingLine) {
1486: LOG.debug("isExpense(AccountingLine) - start");
1487:
1488: boolean returnboolean = AccountingDocumentRuleUtil
1489: .isExpense(accountingLine);
1490: LOG.debug("isExpense(AccountingLine) - end");
1491: return returnboolean;
1492: }
1493:
1494: /**
1495: * Determines whether an accounting line is an expense or asset.
1496: *
1497: * @param line
1498: * @return boolean True if it's an expense or asset.
1499: */
1500: public final boolean isExpenseOrAsset(AccountingLine line) {
1501: LOG.debug("isExpenseOrAsset(AccountingLine) - start");
1502:
1503: boolean returnboolean = isAsset(line) || isExpense(line);
1504: LOG.debug("isExpenseOrAsset(AccountingLine) - end");
1505: return returnboolean;
1506: }
1507:
1508: /**
1509: * Determines whether an accounting line is an income or liability line.
1510: *
1511: * @param line
1512: * @return boolean True if the line is an income or liability line.
1513: */
1514: public final boolean isIncomeOrLiability(AccountingLine line) {
1515: LOG.debug("isIncomeOrLiability(AccountingLine) - start");
1516:
1517: boolean returnboolean = isLiability(line) || isIncome(line);
1518: LOG.debug("isIncomeOrLiability(AccountingLine) - end");
1519: return returnboolean;
1520: }
1521:
1522: /**
1523: * Check object code type to determine whether the accounting line is revenue.
1524: *
1525: * @param line
1526: * @return boolean True if the line is a revenue line.
1527: */
1528: public final boolean isRevenue(AccountingLine line) {
1529: LOG.debug("isRevenue(AccountingLine) - start");
1530:
1531: boolean returnboolean = !isExpense(line);
1532: LOG.debug("isRevenue(AccountingLine) - end");
1533: return returnboolean;
1534: }
1535:
1536: /**
1537: * GLPE amounts are ALWAYS positive, so just take the absolute value of the accounting line's amount.
1538: *
1539: * @param accountingLine
1540: * @return KualiDecimal The amount that will be used to populate the GLPE.
1541: */
1542: protected KualiDecimal getGeneralLedgerPendingEntryAmountForAccountingLine(
1543: AccountingLine accountingLine) {
1544: LOG
1545: .debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
1546:
1547: KualiDecimal returnKualiDecimal = accountingLine.getAmount()
1548: .abs();
1549: LOG
1550: .debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
1551: return returnKualiDecimal;
1552: }
1553:
1554: /**
1555: * Determines whether an accounting line represents a credit line.
1556: *
1557: * @param accountingLine
1558: * @param financialDocument
1559: * @return boolean True if the line is a credit line.
1560: * @throws IllegalStateException
1561: */
1562: public boolean isCredit(AccountingLine accountingLine,
1563: AccountingDocument financialDocument)
1564: throws IllegalStateException {
1565: LOG
1566: .debug("isCredit(AccountingLine, AccountingDocument) - start");
1567:
1568: boolean returnboolean = !isDebit(financialDocument,
1569: accountingLine);
1570: LOG.debug("isCredit(AccountingLine, AccountingDocument) - end");
1571: return returnboolean;
1572: }
1573:
1574: /**
1575: * This method checks to see if the object code for the passed in accounting line exists in the list of restricted object codes.
1576: * Note, the values that this checks against can be externally configured with the ApplicationParameter maintenance mechanism.
1577: *
1578: * @param accountingLine
1579: * @return boolean True if the use of the object code is allowed.
1580: */
1581: public boolean isObjectCodeAllowed(Class documentClass,
1582: AccountingLine accountingLine) {
1583: return isAccountingLineValueAllowed(documentClass,
1584: accountingLine, RESTRICTED_OBJECT_CODES,
1585: KFSPropertyConstants.FINANCIAL_OBJECT_CODE,
1586: accountingLine.getFinancialObjectCode());
1587: }
1588:
1589: /**
1590: * This method returns the document class associated with this accounting document and is used to find the appropriate parameter
1591: * rule This can be overridden to return a different class depending on the situation, initially this is used for Year End
1592: * documents so that they use the same rules as their parent docs
1593: *
1594: * @see org.kuali.module.financial.rules.YearEndGeneralErrorCorrectionDocumentRule#getAccountingLineDocumentClass(AccountingDocument)
1595: * @param financialDocument
1596: * @return documentClass associated with this accounting document
1597: */
1598: protected Class getAccountingLineDocumentClass(
1599: AccountingDocument financialDocument) {
1600: return financialDocument.getClass();
1601: }
1602:
1603: private boolean isAccountingLineValueAllowed(
1604: AccountingDocument accountingDocument,
1605: AccountingLine accountingLine, String parameterName,
1606: String propertyName) {
1607: return isAccountingLineValueAllowed(accountingDocument
1608: .getClass(), accountingLine, parameterName,
1609: propertyName, propertyName);
1610: }
1611:
1612: private boolean isAccountingLineValueAllowed(Class documentClass,
1613: AccountingLine accountingLine, String parameterName,
1614: String propertyName, String userEnteredPropertyName) {
1615: boolean isAllowed = true;
1616: String exceptionMessage = "Invalue property name provided to AccountingDocumentRuleBase isAccountingLineValueAllowed method: "
1617: + propertyName;
1618: try {
1619: String propertyValue = (String) PropertyUtils.getProperty(
1620: accountingLine, propertyName);
1621: if (getParameterService()
1622: .parameterExists(
1623: ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1624: parameterName)) {
1625: isAllowed = getParameterService()
1626: .getParameterEvaluator(
1627: ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1628: parameterName, propertyValue)
1629: .evaluateAndAddError(
1630: SourceAccountingLine.class,
1631: propertyName, userEnteredPropertyName);
1632: }
1633: if (getParameterService().parameterExists(documentClass,
1634: parameterName)) {
1635: isAllowed = getParameterService()
1636: .getParameterEvaluator(documentClass,
1637: parameterName, propertyValue)
1638: .evaluateAndAddError(
1639: SourceAccountingLine.class,
1640: propertyName, userEnteredPropertyName);
1641: }
1642: } catch (IllegalAccessException e) {
1643: throw new RuntimeException(exceptionMessage, e);
1644: } catch (InvocationTargetException e) {
1645: throw new RuntimeException(exceptionMessage, e);
1646: } catch (NoSuchMethodException e) {
1647: throw new RuntimeException(exceptionMessage, e);
1648: }
1649: return isAllowed;
1650: }
1651:
1652: /**
1653: * This checks the accounting line's object type code to ensure that it is not a fund balance object type. This is a universal
1654: * business rule that all transaction processing documents should abide by.
1655: *
1656: * @param accountingLine
1657: * @return boolean
1658: */
1659: public boolean isObjectTypeAllowed(Class documentClass,
1660: AccountingLine accountingLine) {
1661: return isAccountingLineValueAllowed(documentClass,
1662: accountingLine, RESTRICTED_OBJECT_TYPE_CODES,
1663: "objectCode.financialObjectTypeCode",
1664: KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1665: }
1666:
1667: /**
1668: * This method checks to see if the fund group code for the accouting line's account is allowed. The common implementation
1669: * allows any fund group code.
1670: *
1671: * @param accountingLine
1672: * @return boolean
1673: */
1674: public boolean isFundGroupAllowed(Class documentClass,
1675: AccountingLine accountingLine) {
1676: return isAccountingLineValueAllowed(documentClass,
1677: accountingLine, RESTRICTED_FUND_GROUP_CODES,
1678: "account.subFundGroup.fundGroupCode", "accountNumber");
1679: }
1680:
1681: /**
1682: * This method checks to see if the sub fund group code for the accounting line's account is allowed. The common implementation
1683: * allows any sub fund group code.
1684: *
1685: * @param accountingLine
1686: * @return boolean
1687: */
1688: public boolean isSubFundGroupAllowed(Class documentClass,
1689: AccountingLine accountingLine) {
1690: return isAccountingLineValueAllowed(documentClass,
1691: accountingLine, RESTRICTED_SUB_FUND_GROUP_CODES,
1692: "account.subFundGroupCode", "accountNumber");
1693: }
1694:
1695: /**
1696: * This method checks to see if the object sub-type code for the accounting line's object code is allowed. The common
1697: * implementation allows any object sub-type code.
1698: *
1699: * @param accountingLine
1700: * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1701: */
1702: public boolean isObjectSubTypeAllowed(Class documentClass,
1703: AccountingLine accountingLine) {
1704: return isAccountingLineValueAllowed(documentClass,
1705: accountingLine, RESTRICTED_OBJECT_SUB_TYPE_CODES,
1706: "objectCode.financialObjectSubTypeCode",
1707: KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1708: }
1709:
1710: /**
1711: * This method checks to see if the object level for the accounting line's object code is allowed. The common implementation
1712: * allows any object level.
1713: *
1714: * @param accountingLine
1715: * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1716: */
1717: public boolean isObjectLevelAllowed(Class documentClass,
1718: AccountingLine accountingLine) {
1719: return isAccountingLineValueAllowed(documentClass,
1720: accountingLine, RESTRICTED_OBJECT_LEVELS,
1721: "objectCode.financialObjectLevelCode",
1722: KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1723: }
1724:
1725: /**
1726: * This method checks to see if the object consolidation for the accouting line's object code is allowed. The common
1727: * implementation allows any object consolidation.
1728: *
1729: * @param accountingLine
1730: * @return boolean True if the use of the object code's object sub type code is allowed; false otherwise.
1731: */
1732: public boolean isObjectConsolidationAllowed(Class documentClass,
1733: AccountingLine accountingLine) {
1734: return isAccountingLineValueAllowed(
1735: documentClass,
1736: accountingLine,
1737: RESTRICTED_OBJECT_CONSOLIDATIONS,
1738: "objectCode.financialObjectLevel.financialConsolidationObjectCode",
1739: KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
1740: }
1741:
1742: /**
1743: * Determines whether the <code>objectTypeCode</code> is an asset.
1744: *
1745: * @param objectTypeCode
1746: * @return Is she asset or something completely different?
1747: */
1748: public final boolean isAssetTypeCode(String objectTypeCode) {
1749: LOG.debug("isAssetTypeCode(String) - start");
1750:
1751: boolean returnboolean = SpringContext.getBean(
1752: OptionsService.class).getCurrentYearOptions()
1753: .getFinancialObjectTypeAssetsCd()
1754: .equals(objectTypeCode);
1755: LOG.debug("isAssetTypeCode(String) - end");
1756: return returnboolean;
1757: }
1758:
1759: /**
1760: * Determines whether the <code>objectTypeCode</code> is a liability.
1761: *
1762: * @param objectTypeCode
1763: * @return Is she liability or something completely different?
1764: */
1765: public final boolean isLiabilityTypeCode(String objectTypeCode) {
1766: LOG.debug("isLiabilityTypeCode(String) - start");
1767:
1768: boolean returnboolean = SpringContext.getBean(
1769: OptionsService.class).getCurrentYearOptions()
1770: .getFinObjectTypeLiabilitiesCode().equals(
1771: objectTypeCode);
1772: LOG.debug("isLiabilityTypeCode(String) - end");
1773: return returnboolean;
1774: }
1775:
1776: /**
1777: * This method...
1778: *
1779: * @param objectCode
1780: * @return boolean
1781: */
1782: public final boolean isFundBalanceCode(String objectCode) {
1783: LOG.debug("isFundBalanceCode(String) - start");
1784:
1785: boolean returnboolean = (CONSOLIDATED_OBJECT_CODE.FUND_BALANCE
1786: .equals(objectCode));
1787: LOG.debug("isFundBalanceCode(String) - end");
1788: return returnboolean;
1789: }
1790:
1791: /**
1792: * This method...
1793: *
1794: * @param objectCode
1795: * @return boolean
1796: */
1797: public final boolean isBudgetOnlyCodesSubType(String objectCode) {
1798: LOG.debug("isBudgetOnlyCodesSubType(String) - start");
1799:
1800: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.BUDGET_ONLY
1801: .equals(objectCode));
1802: LOG.debug("isBudgetOnlyCodesSubType(String) - end");
1803: return returnboolean;
1804: }
1805:
1806: /**
1807: * This method...
1808: *
1809: * @param objectCode
1810: * @return boolean
1811: */
1812: public final boolean isCashSubType(String objectCode) {
1813: LOG.debug("isCashSubType(String) - start");
1814:
1815: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.CASH
1816: .equals(objectCode));
1817: LOG.debug("isCashSubType(String) - end");
1818: return returnboolean;
1819: }
1820:
1821: /**
1822: * This method...
1823: *
1824: * @param objectCode
1825: * @return boolean
1826: */
1827: public final boolean isFundBalanceSubType(String objectCode) {
1828: LOG.debug("isFundBalanceSubType(String) - start");
1829:
1830: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.SUBTYPE_FUND_BALANCE
1831: .equals(objectCode));
1832: LOG.debug("isFundBalanceSubType(String) - end");
1833: return returnboolean;
1834: }
1835:
1836: /**
1837: * This method...
1838: *
1839: * @param objectCode
1840: * @return boolean
1841: */
1842: public final boolean isHourlyWagesSubType(String objectCode) {
1843: LOG.debug("isHourlyWagesSubType(String) - start");
1844:
1845: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.HOURLY_WAGES
1846: .equals(objectCode));
1847: LOG.debug("isHourlyWagesSubType(String) - end");
1848: return returnboolean;
1849: }
1850:
1851: /**
1852: * This method...
1853: *
1854: * @param objectCode
1855: * @return boolean
1856: */
1857: public final boolean isSalariesSubType(String objectCode) {
1858: LOG.debug("isSalariesSubType(String) - start");
1859:
1860: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.SALARIES
1861: .equals(objectCode));
1862: LOG.debug("isSalariesSubType(String) - end");
1863: return returnboolean;
1864: }
1865:
1866: /**
1867: * This method...
1868: *
1869: * @param objectSubTypeCode
1870: * @return boolean
1871: */
1872: public final boolean isValuationsAndAdjustmentsSubType(
1873: String objectSubTypeCode) {
1874: LOG.debug("isValuationsAndAdjustmentsSubType(String) - start");
1875:
1876: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.VALUATIONS_AND_ADJUSTMENTS
1877: .equals(objectSubTypeCode));
1878: LOG.debug("isValuationsAndAdjustmentsSubType(String) - end");
1879: return returnboolean;
1880: }
1881:
1882: /**
1883: * This method determines whether an object sub-type code is a mandatory transfer or not.
1884: *
1885: * @param objectSubTypeCode
1886: * @return True if it is a manadatory transfer, false otherwise.
1887: */
1888: public final boolean isMandatoryTransfersSubType(
1889: String objectSubTypeCode) {
1890: LOG.debug("isMandatoryTransfersSubType(String) - start");
1891:
1892: boolean returnboolean = checkMandatoryTransfersSubType(
1893: objectSubTypeCode,
1894: APPLICATION_PARAMETER.MANDATORY_TRANSFER_SUBTYPE_CODES);
1895: LOG.debug("isMandatoryTransfersSubType(String) - end");
1896: return returnboolean;
1897: }
1898:
1899: /**
1900: * This method determines whether an object sub-type code is a non-mandatory transfer or not.
1901: *
1902: * @param objectSubTypeCode
1903: * @return True if it is a non-mandatory transfer, false otherwise.
1904: */
1905: public final boolean isNonMandatoryTransfersSubType(
1906: String objectSubTypeCode) {
1907: LOG.debug("isNonMandatoryTransfersSubType(String) - start");
1908:
1909: boolean returnboolean = checkMandatoryTransfersSubType(
1910: objectSubTypeCode,
1911: APPLICATION_PARAMETER.NONMANDATORY_TRANSFER_SUBTYPE_CODES);
1912: LOG.debug("isNonMandatoryTransfersSubType(String) - end");
1913: return returnboolean;
1914: }
1915:
1916: /**
1917: * Helper method for checking the isMandatoryTransfersSubType() and isNonMandatoryTransfersSubType().
1918: *
1919: * @param objectSubTypeCode
1920: * @param parameterName
1921: * @return boolean
1922: */
1923: private final boolean checkMandatoryTransfersSubType(
1924: String objectSubTypeCode, String parameterName) {
1925: LOG
1926: .debug("checkMandatoryTransfersSubType(String, String) - start");
1927:
1928: if (objectSubTypeCode == null) {
1929: throw new IllegalArgumentException(
1930: EXCEPTIONS.NULL_OBJECT_SUBTYPE_MESSAGE);
1931: }
1932: ParameterEvaluator evaluator = getParameterService()
1933: .getParameterEvaluator(
1934: ParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class,
1935: parameterName, objectSubTypeCode);
1936: boolean returnboolean = evaluator.evaluationSucceeds();
1937: LOG
1938: .debug("checkMandatoryTransfersSubType(String, String) - end");
1939: return returnboolean;
1940: }
1941:
1942: /**
1943: * @param objectCode
1944: * @return boolean
1945: */
1946: public final boolean isFringeBenefitsSubType(String objectCode) {
1947: LOG.debug("isFringeBenefitsSubType(String) - start");
1948:
1949: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.FRINGE_BEN
1950: .equals(objectCode));
1951: LOG.debug("isFringeBenefitsSubType(String) - end");
1952: return returnboolean;
1953: }
1954:
1955: /**
1956: * @param objectCode
1957: * @return boolean
1958: */
1959: public final boolean isCostRecoveryExpenseSubType(String objectCode) {
1960: LOG.debug("isCostRecoveryExpenseSubType(String) - start");
1961:
1962: boolean returnboolean = (OBJECT_SUB_TYPE_CODE.COST_RECOVERY_EXPENSE
1963: .equals(objectCode));
1964: LOG.debug("isCostRecoveryExpenseSubType(String) - end");
1965: return returnboolean;
1966: }
1967:
1968: /**
1969: * This method will make sure that totals for a specified set of fund groups is valid across the two different accounting line
1970: * sections.
1971: *
1972: * @param tranDoc
1973: * @param fundGroupCodes An array of the fund group codes that will be considered for balancing.
1974: * @return True if they balance; false otherwise.
1975: */
1976: protected boolean isFundGroupSetBalanceValid(
1977: AccountingDocument tranDoc, Class componentClass,
1978: String parameterName) {
1979: LOG
1980: .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - start");
1981:
1982: // don't need to do any of this if there's no parameter
1983: if (!getParameterService().parameterExists(componentClass,
1984: parameterName)) {
1985: LOG
1986: .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - end");
1987: return true;
1988: }
1989:
1990: List lines = new ArrayList();
1991:
1992: lines.addAll(tranDoc.getSourceAccountingLines());
1993: lines.addAll(tranDoc.getTargetAccountingLines());
1994:
1995: KualiDecimal sourceLinesTotal = new KualiDecimal(0);
1996: KualiDecimal targetLinesTotal = new KualiDecimal(0);
1997:
1998: // iterate over each accounting line and if it has an account with a
1999: // fund group that should be balanced, then add that lines amount to the bucket
2000: for (Iterator i = lines.iterator(); i.hasNext();) {
2001: AccountingLine line = (AccountingLine) i.next();
2002: String fundGroupCode = line.getAccount().getSubFundGroup()
2003: .getFundGroupCode();
2004:
2005: ParameterEvaluator evaluator = getParameterService()
2006: .getParameterEvaluator(componentClass,
2007: parameterName, fundGroupCode);
2008: if (evaluator.evaluationSucceeds()) {
2009: KualiDecimal glpeLineAmount = getGeneralLedgerPendingEntryAmountForAccountingLine(line);
2010: if (line.isSourceAccountingLine()) {
2011: sourceLinesTotal = sourceLinesTotal
2012: .add(glpeLineAmount);
2013: } else {
2014: targetLinesTotal = targetLinesTotal
2015: .add(glpeLineAmount);
2016: }
2017: }
2018: }
2019:
2020: // check that the amounts balance across sections
2021: boolean isValid = true;
2022:
2023: if (sourceLinesTotal.compareTo(targetLinesTotal) != 0) {
2024: isValid = false;
2025:
2026: // creating an evaluator to just format the fund codes into a nice string
2027: ParameterEvaluator evaluator = getParameterService()
2028: .getParameterEvaluator(componentClass,
2029: parameterName, "");
2030: GlobalVariables
2031: .getErrorMap()
2032: .putError(
2033: "document.sourceAccountingLines",
2034: ERROR_DOCUMENT_FUND_GROUP_SET_DOES_NOT_BALANCE,
2035: new String[] {
2036: tranDoc
2037: .getSourceAccountingLinesSectionTitle(),
2038: tranDoc
2039: .getTargetAccountingLinesSectionTitle(),
2040: evaluator
2041: .getParameterValuesForMessage() });
2042: }
2043:
2044: LOG
2045: .debug("isFundGroupSetBalanceValid(AccountingDocument, String[]) - end");
2046: return isValid;
2047: }
2048:
2049: /**
2050: * A helper method which builds out a human readable string of the fund group codes that were used for the balancing rule.
2051: *
2052: * @param fundGroupCodes
2053: * @return String
2054: */
2055: private String buildFundGroupCodeBalancingErrorMessage(
2056: String[] fundGroupCodes) {
2057:
2058: LOG
2059: .debug("buildFundGroupCodeBalancingErrorMessage(String[]) - start");
2060:
2061: String balancingFundGroups = "";
2062: int arrayLen = fundGroupCodes.length;
2063: if (arrayLen == 1) {
2064: balancingFundGroups = fundGroupCodes[0];
2065: } else {
2066: if (arrayLen == 2) {
2067: balancingFundGroups = fundGroupCodes[0] + " or "
2068: + fundGroupCodes[1];
2069: } else {
2070: for (int i = 0; i < arrayLen; i++) {
2071: String balancingFundGroupCode = fundGroupCodes[i];
2072: if ((i + 1) == arrayLen) {
2073: balancingFundGroups = balancingFundGroups
2074: + ", or " + balancingFundGroupCode;
2075: } else {
2076: balancingFundGroups = balancingFundGroups
2077: + ", " + balancingFundGroupCode;
2078: }
2079: }
2080: }
2081: }
2082:
2083: LOG
2084: .debug("buildFundGroupCodeBalancingErrorMessage(String[]) - end");
2085: return balancingFundGroups;
2086: }
2087:
2088: /**
2089: * Convience method for determine if a document is an error correction document.
2090: *
2091: * @param accountingDocument
2092: * @return true if document is an error correct
2093: */
2094: protected boolean isErrorCorrection(
2095: AccountingDocument accountingDocument) {
2096: LOG.debug("isErrorCorrection(AccountingDocument) - start");
2097:
2098: boolean isErrorCorrection = false;
2099:
2100: String correctsDocumentId = accountingDocument
2101: .getDocumentHeader()
2102: .getFinancialDocumentInErrorNumber();
2103: if (StringUtils.isNotBlank(correctsDocumentId)) {
2104: isErrorCorrection = true;
2105: }
2106:
2107: LOG.debug("isErrorCorrection(AccountingDocument) - end");
2108: return isErrorCorrection;
2109: }
2110:
2111: /**
2112: * util class that contains common algorithms for determining debit amounts
2113: */
2114: public static class IsDebitUtils {
2115: public static final String isDebitCalculationIllegalStateExceptionMessage = "an invalid debit/credit check state was detected";
2116: public static final String isErrorCorrectionIllegalStateExceptionMessage = "invalid (error correction) document not allowed";
2117: public static final String isInvalidLineTypeIllegalArgumentExceptionMessage = "invalid accounting line type";
2118:
2119: /**
2120: * @param debitCreditCode
2121: * @return true if debitCreditCode equals the the debit constant
2122: */
2123: public static boolean isDebitCode(String debitCreditCode) {
2124: LOG.debug("isDebitCode(String) - start");
2125:
2126: boolean returnboolean = StringUtils.equals(
2127: KFSConstants.GL_DEBIT_CODE, debitCreditCode);
2128: LOG.debug("isDebitCode(String) - end");
2129: return returnboolean;
2130: }
2131:
2132: /**
2133: * <ol>
2134: * <li>object type is included in determining if a line is debit or credit.
2135: * </ol>
2136: * the following are credits (return false)
2137: * <ol>
2138: * <li> (isIncome || isLiability) && (lineAmount > 0)
2139: * <li> (isExpense || isAsset) && (lineAmount < 0)
2140: * </ol>
2141: * the following are debits (return true)
2142: * <ol>
2143: * <li> (isIncome || isLiability) && (lineAmount < 0)
2144: * <li> (isExpense || isAsset) && (lineAmount > 0)
2145: * </ol>
2146: * the following are invalid ( throws an <code>IllegalStateException</code>)
2147: * <ol>
2148: * <li> document isErrorCorrection
2149: * <li> lineAmount == 0
2150: * <li> ! (isIncome || isLiability || isExpense || isAsset)
2151: * </ol>
2152: *
2153: * @param rule
2154: * @param accountingDocument
2155: * @param accountingLine
2156: * @return boolean
2157: */
2158: public static boolean isDebitConsideringType(
2159: PurapAccountingDocumentRuleBase rule,
2160: AccountingDocument accountingDocument,
2161: AccountingLine accountingLine) {
2162: LOG
2163: .debug("isDebitConsideringType(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2164:
2165: KualiDecimal amount = accountingLine.getAmount();
2166: // zero amounts are not allowed
2167: if (amount.isZero()) {
2168: throw new IllegalStateException(
2169: isDebitCalculationIllegalStateExceptionMessage);
2170: }
2171: boolean isDebit = false;
2172: boolean isPositiveAmount = accountingLine.getAmount()
2173: .isPositive();
2174:
2175: // income/liability
2176: if (rule.isIncomeOrLiability(accountingLine)) {
2177: isDebit = !isPositiveAmount;
2178: }
2179: // expense/asset
2180: else {
2181: if (rule.isExpenseOrAsset(accountingLine)) {
2182: isDebit = isPositiveAmount;
2183: } else {
2184: throw new IllegalStateException(
2185: isDebitCalculationIllegalStateExceptionMessage);
2186: }
2187: }
2188:
2189: LOG
2190: .debug("isDebitConsideringType(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2191: return isDebit;
2192: }
2193:
2194: /**
2195: * <ol>
2196: * <li>object type is not included in determining if a line is debit or credit.
2197: * <li>accounting line section (source/target) is not included in determining if a line is debit or credit.
2198: * </ol>
2199: * the following are credits (return false)
2200: * <ol>
2201: * <li> none
2202: * </ol>
2203: * the following are debits (return true)
2204: * <ol>
2205: * <li> (isIncome || isLiability || isExpense || isAsset) && (lineAmount > 0)
2206: * </ol>
2207: * the following are invalid ( throws an <code>IllegalStateException</code>)
2208: * <ol>
2209: * <li> lineAmount <= 0
2210: * <li> ! (isIncome || isLiability || isExpense || isAsset)
2211: * </ol>
2212: *
2213: * @param rule
2214: * @param accountingDocument
2215: * @param accountingLine
2216: * @return boolean
2217: */
2218: public static boolean isDebitConsideringNothingPositiveOnly(
2219: PurapAccountingDocumentRuleBase rule,
2220: AccountingDocument accountingDocument,
2221: AccountingLine accountingLine) {
2222: LOG
2223: .debug("isDebitConsideringNothingPositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2224:
2225: boolean isDebit = false;
2226: KualiDecimal amount = accountingLine.getAmount();
2227: boolean isPositiveAmount = amount.isPositive();
2228: // isDebit if income/liability/expense/asset and line amount is positive
2229: if (isPositiveAmount
2230: && (rule.isIncomeOrLiability(accountingLine) || rule
2231: .isExpenseOrAsset(accountingLine))) {
2232: isDebit = true;
2233: } else {
2234: // non error correction
2235: if (!rule.isErrorCorrection(accountingDocument)) {
2236: throw new IllegalStateException(
2237: isDebitCalculationIllegalStateExceptionMessage);
2238:
2239: }
2240: // error correction
2241: else {
2242: isDebit = false;
2243: }
2244: }
2245:
2246: LOG
2247: .debug("isDebitConsideringNothingPositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2248: return isDebit;
2249: }
2250:
2251: /**
2252: * <ol>
2253: * <li>accounting line section (source/target) type is included in determining if a line is debit or credit.
2254: * <li> zero line amounts are never allowed
2255: * </ol>
2256: * the following are credits (return false)
2257: * <ol>
2258: * <li> isSourceLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount > 0)
2259: * <li> isTargetLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount < 0)
2260: * </ol>
2261: * the following are debits (return true)
2262: * <ol>
2263: * <li> isSourceLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount < 0)
2264: * <li> isTargetLine && (isIncome || isExpense || isAsset || isLiability) && (lineAmount > 0)
2265: * </ol>
2266: * the following are invalid ( throws an <code>IllegalStateException</code>)
2267: * <ol>
2268: * <li> lineAmount == 0
2269: * <li> ! (isIncome || isLiability || isExpense || isAsset)
2270: * </ol>
2271: *
2272: * @param rule
2273: * @param accountingDocument
2274: * @param accountingLine
2275: * @return boolean
2276: */
2277: public static boolean isDebitConsideringSection(
2278: PurapAccountingDocumentRuleBase rule,
2279: AccountingDocument accountingDocument,
2280: AccountingLine accountingLine) {
2281: LOG
2282: .debug("isDebitConsideringSection(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2283:
2284: KualiDecimal amount = accountingLine.getAmount();
2285: // zero amounts are not allowed
2286: if (amount.isZero()) {
2287: throw new IllegalStateException(
2288: isDebitCalculationIllegalStateExceptionMessage);
2289: }
2290: boolean isDebit = false;
2291: boolean isPositiveAmount = accountingLine.getAmount()
2292: .isPositive();
2293: // source line
2294: if (accountingLine.isSourceAccountingLine()) {
2295: // income/liability/expense/asset
2296: if (rule.isIncomeOrLiability(accountingLine)
2297: || rule.isExpenseOrAsset(accountingLine)) {
2298: isDebit = !isPositiveAmount;
2299: } else {
2300: throw new IllegalStateException(
2301: isDebitCalculationIllegalStateExceptionMessage);
2302: }
2303: }
2304: // target line
2305: else {
2306: if (accountingLine.isTargetAccountingLine()) {
2307: if (rule.isIncomeOrLiability(accountingLine)
2308: || rule.isExpenseOrAsset(accountingLine)) {
2309: isDebit = isPositiveAmount;
2310: } else {
2311: throw new IllegalStateException(
2312: isDebitCalculationIllegalStateExceptionMessage);
2313: }
2314: } else {
2315: throw new IllegalArgumentException(
2316: isInvalidLineTypeIllegalArgumentExceptionMessage);
2317: }
2318: }
2319:
2320: LOG
2321: .debug("isDebitConsideringSection(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2322: return isDebit;
2323: }
2324:
2325: /**
2326: * <ol>
2327: * <li>accounting line section (source/target) and object type is included in determining if a line is debit or credit.
2328: * <li> negative line amounts are <b>Only</b> allowed during error correction
2329: * </ol>
2330: * the following are credits (return false)
2331: * <ol>
2332: * <li> isSourceLine && (isExpense || isAsset) && (lineAmount > 0)
2333: * <li> isTargetLine && (isIncome || isLiability) && (lineAmount > 0)
2334: * <li> isErrorCorrection && isSourceLine && (isIncome || isLiability) && (lineAmount < 0)
2335: * <li> isErrorCorrection && isTargetLine && (isExpense || isAsset) && (lineAmount < 0)
2336: * </ol>
2337: * the following are debits (return true)
2338: * <ol>
2339: * <li> isSourceLine && (isIncome || isLiability) && (lineAmount > 0)
2340: * <li> isTargetLine && (isExpense || isAsset) && (lineAmount > 0)
2341: * <li> isErrorCorrection && (isExpense || isAsset) && (lineAmount < 0)
2342: * <li> isErrorCorrection && (isIncome || isLiability) && (lineAmount < 0)
2343: * </ol>
2344: * the following are invalid ( throws an <code>IllegalStateException</code>)
2345: * <ol>
2346: * <li> !isErrorCorrection && !(lineAmount > 0)
2347: * </ol>
2348: *
2349: * @param rule
2350: * @param accountingDocument
2351: * @param accountingLine
2352: * @return boolean
2353: */
2354: public static boolean isDebitConsideringSectionAndTypePositiveOnly(
2355: PurapAccountingDocumentRuleBase rule,
2356: AccountingDocument accountingDocument,
2357: AccountingLine accountingLine) {
2358: LOG
2359: .debug("isDebitConsideringSectionAndTypePositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - start");
2360:
2361: boolean isDebit = false;
2362: KualiDecimal amount = accountingLine.getAmount();
2363: boolean isPositiveAmount = amount.isPositive();
2364: // non error correction - only allow amount >0
2365: if (!isPositiveAmount
2366: && !rule.isErrorCorrection(accountingDocument)) {
2367: throw new IllegalStateException(
2368: isDebitCalculationIllegalStateExceptionMessage);
2369: }
2370: // source line
2371: if (accountingLine.isSourceAccountingLine()) {
2372: // could write below block in one line using == as XNOR operator, but that's confusing to read:
2373: // isDebit = (rule.isIncomeOrLiability(accountingLine) == isPositiveAmount);
2374: if (isPositiveAmount) {
2375: isDebit = rule.isIncomeOrLiability(accountingLine);
2376: } else {
2377: isDebit = rule.isExpenseOrAsset(accountingLine);
2378: }
2379: }
2380: // target line
2381: else {
2382: if (accountingLine.isTargetAccountingLine()) {
2383: if (isPositiveAmount) {
2384: isDebit = rule.isExpenseOrAsset(accountingLine);
2385: } else {
2386: isDebit = rule
2387: .isIncomeOrLiability(accountingLine);
2388: }
2389: } else {
2390: throw new IllegalArgumentException(
2391: isInvalidLineTypeIllegalArgumentExceptionMessage);
2392: }
2393: }
2394:
2395: LOG
2396: .debug("isDebitConsideringSectionAndTypePositiveOnly(AccountingDocumentRuleBase, AccountingDocument, AccountingLine) - end");
2397: return isDebit;
2398: }
2399:
2400: /**
2401: * throws an <code>IllegalStateException</code> if the document is an error correction. otherwise does nothing
2402: *
2403: * @param rule
2404: * @param accountingDocument
2405: */
2406: public static void disallowErrorCorrectionDocumentCheck(
2407: PurapAccountingDocumentRuleBase rule,
2408: AccountingDocument accountingDocument) {
2409: LOG
2410: .debug("disallowErrorCorrectionDocumentCheck(AccountingDocumentRuleBase, AccountingDocument) - start");
2411:
2412: if (rule.isErrorCorrection(accountingDocument)) {
2413: throw new IllegalStateException(
2414: isErrorCorrectionIllegalStateExceptionMessage);
2415: }
2416:
2417: LOG
2418: .debug("disallowErrorCorrectionDocumentCheck(AccountingDocumentRuleBase, AccountingDocument) - end");
2419: }
2420: }
2421: }
|