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