0001: /*
0002: * Copyright 2005-2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.financial.rules;
0017:
0018: import static org.kuali.kfs.KFSConstants.ACCOUNTING_LINE_ERRORS;
0019: import static org.kuali.kfs.KFSConstants.ACCOUNTING_PERIOD_STATUS_CLOSED;
0020: import static org.kuali.kfs.KFSConstants.ACCOUNTING_PERIOD_STATUS_CODE_FIELD;
0021: import static org.kuali.kfs.KFSConstants.AUXILIARY_LINE_HELPER_PROPERTY_NAME;
0022: import static org.kuali.kfs.KFSConstants.CREDIT_AMOUNT_PROPERTY_NAME;
0023: import static org.kuali.kfs.KFSConstants.DEBIT_AMOUNT_PROPERTY_NAME;
0024: import static org.kuali.kfs.KFSConstants.DOCUMENT_ERRORS;
0025: import static org.kuali.kfs.KFSConstants.GL_CREDIT_CODE;
0026: import static org.kuali.kfs.KFSConstants.GL_DEBIT_CODE;
0027: import static org.kuali.kfs.KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME;
0028: import static org.kuali.kfs.KFSConstants.SQUARE_BRACKET_LEFT;
0029: import static org.kuali.kfs.KFSConstants.SQUARE_BRACKET_RIGHT;
0030: import static org.kuali.kfs.KFSConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
0031: import static org.kuali.kfs.KFSConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
0032: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_PERIOD_CLOSED;
0033: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_TWO_PERIODS;
0034: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_AV_INCORRECT_FISCAL_YEAR_AVRC;
0035: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_AV_INCORRECT_POST_PERIOD_AVRC;
0036: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_BALANCE;
0037: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_BALANCE_CONSIDERING_CREDIT_AND_DEBIT_AMOUNTS;
0038: import static org.kuali.kfs.KFSKeyConstants.ERROR_DOCUMENT_INCORRECT_OBJ_CODE_WITH_SUB_TYPE_OBJ_LEVEL_AND_OBJ_TYPE;
0039: import static org.kuali.kfs.KFSKeyConstants.ERROR_ZERO_OR_NEGATIVE_AMOUNT;
0040: import static org.kuali.kfs.KFSKeyConstants.AuxiliaryVoucher.ERROR_ACCOUNTING_PERIOD_OUT_OF_RANGE;
0041: import static org.kuali.kfs.KFSKeyConstants.AuxiliaryVoucher.ERROR_DIFFERENT_CHARTS;
0042: import static org.kuali.kfs.KFSKeyConstants.AuxiliaryVoucher.ERROR_DIFFERENT_SUB_FUND_GROUPS;
0043: import static org.kuali.kfs.KFSKeyConstants.AuxiliaryVoucher.ERROR_INVALID_ACCRUAL_REVERSAL_DATE;
0044: import static org.kuali.kfs.KFSPropertyConstants.REVERSAL_DATE;
0045: import static org.kuali.kfs.rules.AccountingDocumentRuleBaseConstants.ERROR_PATH.DOCUMENT_ERROR_PREFIX;
0046: import static org.kuali.module.financial.rules.AuxiliaryVoucherDocumentRuleConstants.AUXILIARY_VOUCHER_ACCOUNTING_PERIOD_GRACE_PERIOD;
0047: import static org.kuali.module.financial.rules.AuxiliaryVoucherDocumentRuleConstants.GENERAL_LEDGER_PENDING_ENTRY_OFFSET_CODE;
0048: import static org.kuali.module.financial.rules.AuxiliaryVoucherDocumentRuleConstants.RESTRICTED_COMBINED_CODES;
0049: import static org.kuali.module.financial.rules.AuxiliaryVoucherDocumentRuleConstants.RESTRICTED_PERIOD_CODES;
0050:
0051: import java.sql.Date;
0052: import java.sql.Timestamp;
0053: import java.util.List;
0054:
0055: import org.apache.commons.lang.StringUtils;
0056: import org.apache.commons.logging.Log;
0057: import org.apache.commons.logging.LogFactory;
0058: import org.kuali.core.document.Document;
0059: import org.kuali.core.exceptions.ValidationException;
0060: import org.kuali.core.service.DateTimeService;
0061: import org.kuali.core.service.DocumentTypeService;
0062: import org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper;
0063: import org.kuali.core.util.GlobalVariables;
0064: import org.kuali.core.util.KualiDecimal;
0065: import org.kuali.core.util.ObjectUtils;
0066: import org.kuali.kfs.KFSKeyConstants;
0067: import org.kuali.kfs.KFSPropertyConstants;
0068: import org.kuali.kfs.bo.AccountingLine;
0069: import org.kuali.kfs.bo.GeneralLedgerPendingEntry;
0070: import org.kuali.kfs.bo.Options;
0071: import org.kuali.kfs.context.SpringContext;
0072: import org.kuali.kfs.document.AccountingDocument;
0073: import org.kuali.kfs.rules.AccountingDocumentRuleBase;
0074: import org.kuali.kfs.service.GeneralLedgerPendingEntryService;
0075: import org.kuali.kfs.service.OptionsService;
0076: import org.kuali.kfs.service.ParameterEvaluator;
0077: import org.kuali.kfs.service.ParameterService;
0078: import org.kuali.module.chart.bo.AccountingPeriod;
0079: import org.kuali.module.chart.service.AccountingPeriodService;
0080: import org.kuali.module.financial.document.AuxiliaryVoucherDocument;
0081: import org.kuali.module.financial.document.DistributionOfIncomeAndExpenseDocument;
0082: import org.kuali.module.financial.service.UniversityDateService;
0083: import org.kuali.module.gl.service.SufficientFundsService;
0084:
0085: /**
0086: * Business rule(s) applicable to <code>{@link AuxiliaryVoucherDocument}</code>.
0087: */
0088: public class AuxiliaryVoucherDocumentRule extends
0089: AccountingDocumentRuleBase {
0090: private static Log LOG = LogFactory
0091: .getLog(AuxiliaryVoucherDocumentRule.class);
0092:
0093: /**
0094: * Get from APC the offset object code that is used for the <code>{@link GeneralLedgerPendingEntry}</code>
0095: *
0096: * @return String returns GLPE parameter name
0097: */
0098: protected String getGeneralLedgerPendingEntryOffsetObjectCode() {
0099: return GENERAL_LEDGER_PENDING_ENTRY_OFFSET_CODE;
0100: }
0101:
0102: /**
0103: * Returns true if an accounting line is a debit or credit The following are credits (return false)
0104: * <ol>
0105: * <li> debitCreditCode != 'D'
0106: * </ol>
0107: * the following are debits (return true)
0108: * <ol>
0109: * <li> debitCreditCode == 'D'
0110: * </ol>
0111: * the following are invalid ( throws an <code>IllegalStateException</code>)
0112: * <ol>
0113: * <li> debitCreditCode isBlank
0114: * </ol>
0115: *
0116: * @param financialDocument submitted accounting document
0117: * @param accounttingLine accounting line being tested if it is a debit or not
0118: * @see org.kuali.core.rule.AccountingLineRule#isDebit(org.kuali.core.document.FinancialDocument,
0119: * org.kuali.core.bo.AccountingLine)
0120: */
0121: public boolean isDebit(AccountingDocument FinancialDocument,
0122: AccountingLine accountingLine) throws IllegalStateException {
0123: String debitCreditCode = accountingLine.getDebitCreditCode();
0124: if (StringUtils.isBlank(debitCreditCode)) {
0125: throw new IllegalStateException(
0126: IsDebitUtils.isDebitCalculationIllegalStateExceptionMessage);
0127: }
0128: return IsDebitUtils.isDebitCode(debitCreditCode);
0129: }
0130:
0131: /**
0132: * Overrides the parent to display correct error message for a single sided document
0133: *
0134: * @param financialDocument submitted accounting document
0135: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#isSourceAccountingLinesRequiredNumberForRoutingMet(org.kuali.core.document.FinancialDocument)
0136: */
0137: @Override
0138: protected boolean isSourceAccountingLinesRequiredNumberForRoutingMet(
0139: AccountingDocument financialDocument) {
0140: if (0 == financialDocument.getSourceAccountingLines().size()) {
0141: GlobalVariables
0142: .getErrorMap()
0143: .putError(
0144: DOCUMENT_ERROR_PREFIX
0145: + KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
0146: KFSKeyConstants.ERROR_DOCUMENT_SINGLE_SECTION_NO_ACCOUNTING_LINES);
0147: return false;
0148: } else {
0149: return true;
0150: }
0151: }
0152:
0153: /**
0154: * Overrides the parent to return true, because Auxiliary Voucher documents only use the SourceAccountingLines data structures.
0155: * The list that holds TargetAccountingLines should be empty. This will be checked when the document is "routed" or submitted to
0156: * post - it's called automatically by the parent's processRouteDocument method.
0157: *
0158: * @param financialDocument submitted accounting document
0159: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#isTargetAccountingLinesRequiredNumberForRoutingMet(org.kuali.core.document.FinancialDocument)
0160: */
0161: @Override
0162: protected boolean isTargetAccountingLinesRequiredNumberForRoutingMet(
0163: AccountingDocument financialDocument) {
0164: return true;
0165: }
0166:
0167: /**
0168: * Overrides the parent to return true, because Auxiliary Voucher documents aren't restricted from using any object code. This
0169: * is part of the "save" check that gets done. This method is called automatically by the parent's processSaveDocument method.
0170: *
0171: * @param documentClass submitted document class (not used in overriden method)
0172: * @param accountingLine accountingLine where object code is being checked (not used in overriden method)
0173: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#isObjectCodeAllowed(org.kuali.core.bo.AccountingLine)
0174: */
0175: @Override
0176: public boolean isObjectCodeAllowed(Class documentClass,
0177: AccountingLine accountingLine) {
0178: return true;
0179: }
0180:
0181: /**
0182: * Accounting lines for Auxiliary Vouchers can only be positive non-zero numbers
0183: *
0184: * @param document submitted AccountingDocument
0185: * @param accountingLine accountingLine where amount is being validated
0186: * @return true if amount is NOT 0 or negative
0187: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#isAmountValid(org.kuali.core.document.FinancialDocument,
0188: * org.kuali.core.bo.AccountingLine)
0189: */
0190: @Override
0191: public boolean isAmountValid(AccountingDocument document,
0192: AccountingLine accountingLine) {
0193: boolean retval = true;
0194: KualiDecimal amount = accountingLine.getAmount();
0195:
0196: AuxiliaryVoucherDocument avDoc = (AuxiliaryVoucherDocument) document;
0197:
0198: // check for negative or zero amounts
0199: if (KualiDecimal.ZERO.equals(amount)) { // if 0
0200: GlobalVariables
0201: .getErrorMap()
0202: .putErrorWithoutFullErrorPath(
0203: buildErrorMapKeyPathForDebitCreditAmount(true),
0204: ERROR_ZERO_OR_NEGATIVE_AMOUNT,
0205: "an accounting line");
0206: GlobalVariables
0207: .getErrorMap()
0208: .putErrorWithoutFullErrorPath(
0209: buildErrorMapKeyPathForDebitCreditAmount(false),
0210: ERROR_ZERO_OR_NEGATIVE_AMOUNT,
0211: "an accounting line");
0212:
0213: retval = false;
0214: } else if (amount.isNegative()) { // entered a negative number
0215: String debitCreditCode = accountingLine
0216: .getDebitCreditCode();
0217: if (StringUtils.isNotBlank(debitCreditCode)
0218: && GL_DEBIT_CODE.equals(debitCreditCode)) {
0219: GlobalVariables
0220: .getErrorMap()
0221: .putErrorWithoutFullErrorPath(
0222: buildErrorMapKeyPathForDebitCreditAmount(true),
0223: ERROR_ZERO_OR_NEGATIVE_AMOUNT,
0224: "an accounting line");
0225: } else {
0226: GlobalVariables
0227: .getErrorMap()
0228: .putErrorWithoutFullErrorPath(
0229: buildErrorMapKeyPathForDebitCreditAmount(false),
0230: ERROR_ZERO_OR_NEGATIVE_AMOUNT,
0231: "an accounting line");
0232: }
0233:
0234: retval = false;
0235: }
0236:
0237: return retval;
0238: }
0239:
0240: /**
0241: * This method looks at the current full key path that exists in the ErrorMap structure to determine how to build the error map
0242: * for the special journal voucher credit and debit fields since they don't conform to the standard pattern of accounting lines.
0243: *
0244: * @param isDebit boolean to determine whether or not value isDebit or not
0245: * @return String represents error map key to use
0246: */
0247: private String buildErrorMapKeyPathForDebitCreditAmount(
0248: boolean isDebit) {
0249: // determine if we are looking at a new line add or an update
0250: boolean isNewLineAdd = GlobalVariables.getErrorMap()
0251: .getErrorPath().contains(
0252: NEW_SOURCE_ACCT_LINE_PROPERTY_NAME);
0253: isNewLineAdd |= GlobalVariables.getErrorMap().getErrorPath()
0254: .contains(NEW_SOURCE_ACCT_LINE_PROPERTY_NAME);
0255:
0256: if (isNewLineAdd) {
0257: if (isDebit) {
0258: return DEBIT_AMOUNT_PROPERTY_NAME;
0259: } else {
0260: return CREDIT_AMOUNT_PROPERTY_NAME;
0261: }
0262: } else {
0263: String index = StringUtils.substringBetween(GlobalVariables
0264: .getErrorMap().getKeyPath("", true),
0265: SQUARE_BRACKET_LEFT, SQUARE_BRACKET_RIGHT);
0266: String indexWithParams = SQUARE_BRACKET_LEFT + index
0267: + SQUARE_BRACKET_RIGHT;
0268: if (isDebit) {
0269: return AUXILIARY_LINE_HELPER_PROPERTY_NAME
0270: + indexWithParams
0271: + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
0272: } else {
0273: return AUXILIARY_LINE_HELPER_PROPERTY_NAME
0274: + indexWithParams
0275: + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
0276: }
0277: }
0278: }
0279:
0280: /**
0281: * This method sets the appropriate document type and object type codes into the GLPEs based on the type of AV document chosen.
0282: *
0283: * @param document submitted AccountingDocument
0284: * @param accountingLine represents accounting line where object type code is retrieved from
0285: * @param explicitEntry GeneralPendingLedgerEntry object that has its document type, object type, period code, and fiscal year
0286: * set
0287: * @see FinancialDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(FinancialDocument, AccountingLine,
0288: * GeneralLedgerPendingEntry)
0289: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processExplicitGeneralLedgerPendingEntry(org.kuali.core.document.FinancialDocument,
0290: * org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper, org.kuali.core.bo.AccountingLine,
0291: * org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
0292: */
0293: protected void customizeExplicitGeneralLedgerPendingEntry(
0294: AccountingDocument document, AccountingLine accountingLine,
0295: GeneralLedgerPendingEntry explicitEntry) {
0296: AuxiliaryVoucherDocument auxVoucher = (AuxiliaryVoucherDocument) document;
0297:
0298: java.sql.Date reversalDate = auxVoucher.getReversalDate();
0299: if (reversalDate != null) {
0300: explicitEntry
0301: .setFinancialDocumentReversalDate(reversalDate);
0302: } else {
0303: explicitEntry.setFinancialDocumentReversalDate(null);
0304: }
0305: explicitEntry.setFinancialDocumentTypeCode(auxVoucher
0306: .getTypeCode()); // make sure to use the accrual type as the document
0307: // type
0308: explicitEntry
0309: .setFinancialObjectTypeCode(getObjectTypeCode(accountingLine));
0310: explicitEntry.setUniversityFiscalPeriodCode(auxVoucher
0311: .getPostingPeriodCode()); // use chosen posting period code
0312: explicitEntry.setUniversityFiscalYear(auxVoucher
0313: .getPostingYear()); // use chosen posting year
0314: }
0315:
0316: /**
0317: * An Accrual Voucher only generates offsets if it is a recode (AVRC). So this method overrides to do nothing more than return
0318: * true if it's not a recode. If it is a recode, then it is responsible for generating two offsets with a document type of DI.
0319: *
0320: * @param financialDocument submitted accounting document
0321: * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer
0322: * @param accountingLineCopy accounting line from accounting document
0323: * @param explicitEntry represents explicit entry
0324: * @param offsetEntry represents offset entry
0325: * @return true if general ledger pending entry is processed successfully for accurals, adjustments, and recodes
0326: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processOffsetGeneralLedgerPendingEntry(org.kuali.core.document.FinancialDocument,
0327: * org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper, org.kuali.core.bo.AccountingLine,
0328: * org.kuali.module.gl.bo.GeneralLedgerPendingEntry, org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
0329: */
0330: @Override
0331: protected boolean processOffsetGeneralLedgerPendingEntry(
0332: AccountingDocument financialDocument,
0333: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0334: AccountingLine accountingLineCopy,
0335: GeneralLedgerPendingEntry explicitEntry,
0336: GeneralLedgerPendingEntry offsetEntry) {
0337: AuxiliaryVoucherDocument auxVoucher = (AuxiliaryVoucherDocument) financialDocument;
0338:
0339: // do not generate an offset entry if this is a normal or adjustment AV type
0340: if (auxVoucher.isAccrualType() || auxVoucher.isAdjustmentType()) {
0341: return processOffsetGeneralLedgerPendingEntryForAccrualsAndAdjustments(
0342: financialDocument, sequenceHelper,
0343: accountingLineCopy, explicitEntry, offsetEntry);
0344: } else if (auxVoucher.isRecodeType()) { // recodes generate offsets
0345: return processOffsetGeneralLedgerPendingEntryForRecodes(
0346: financialDocument, sequenceHelper,
0347: accountingLineCopy, explicitEntry, offsetEntry);
0348: } else {
0349: throw new IllegalStateException(
0350: "Illegal auxiliary voucher type: "
0351: + auxVoucher.getTypeCode());
0352: }
0353: }
0354:
0355: /**
0356: * This method handles generating or not generating the appropriate offsets if the AV type is a recode.
0357: *
0358: * @param financialDocument submitted accounting document
0359: * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer
0360: * @param accountingLineCopy accounting line from accounting document
0361: * @param explicitEntry represents explicit entry
0362: * @param offsetEntry represents offset entry
0363: * @return true if offset general ledger pending entry is processed
0364: */
0365: private boolean processOffsetGeneralLedgerPendingEntryForRecodes(
0366: AccountingDocument financialDocument,
0367: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0368: AccountingLine accountingLineCopy,
0369: GeneralLedgerPendingEntry explicitEntry,
0370: GeneralLedgerPendingEntry offsetEntry) {
0371: // the explicit entry has already been generated and added to the list, so to get the right offset, we have to set the value
0372: // of the document type code on the explicit
0373: // to the type code for a DI document so that it gets passed into the next call and we retrieve the right offset definition
0374: // since these offsets are
0375: // specific to Distrib. of Income and Expense documents - we need to do a deep copy though so we don't do this by reference
0376: GeneralLedgerPendingEntry explicitEntryDeepCopy = (GeneralLedgerPendingEntry) ObjectUtils
0377: .deepCopy(explicitEntry);
0378: explicitEntryDeepCopy
0379: .setFinancialDocumentTypeCode(SpringContext
0380: .getBean(DocumentTypeService.class)
0381: .getDocumentTypeCodeByClass(
0382: DistributionOfIncomeAndExpenseDocument.class));
0383:
0384: // set the posting period to current, because DI GLPEs for recodes should post to the current period
0385: java.sql.Date today = SpringContext.getBean(
0386: DateTimeService.class).getCurrentSqlDateMidnight();
0387: explicitEntryDeepCopy
0388: .setUniversityFiscalPeriodCode(SpringContext.getBean(
0389: AccountingPeriodService.class).getByDate(today)
0390: .getUniversityFiscalPeriodCode()); // use
0391: // current
0392: // period
0393: // code
0394:
0395: // call the super to process an offset entry; see the customize method below for AVRC specific attribute values
0396: // pass in the explicit deep copy
0397: boolean success = super .processOffsetGeneralLedgerPendingEntry(
0398: financialDocument, sequenceHelper, accountingLineCopy,
0399: explicitEntryDeepCopy, offsetEntry);
0400:
0401: // increment the sequence appropriately
0402: sequenceHelper.increment();
0403:
0404: // now generate the AVRC DI entry
0405: // pass in the explicit deep copy
0406: success &= processAuxiliaryVoucherRecodeDistributionOfIncomeAndExpenseGeneralLedgerPendingEntry(
0407: financialDocument, sequenceHelper,
0408: explicitEntryDeepCopy);
0409:
0410: return success;
0411: }
0412:
0413: /**
0414: * This method handles generating or not generating the appropriate offsets if the AV type is accrual or adjustment.
0415: *
0416: * @param financialDocument submitted accounting document
0417: * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer
0418: * @param accountingLineCopy accounting line from accounting document
0419: * @param explicitEntry represents explicit entry
0420: * @param offsetEntry represents offset entry
0421: * @return true if offset general ledger pending entry is processed successfully
0422: */
0423: private boolean processOffsetGeneralLedgerPendingEntryForAccrualsAndAdjustments(
0424: AccountingDocument financialDocument,
0425: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0426: AccountingLine accountingLineCopy,
0427: GeneralLedgerPendingEntry explicitEntry,
0428: GeneralLedgerPendingEntry offsetEntry) {
0429: boolean success = true;
0430:
0431: if (isDocumentForMultipleAccounts(financialDocument)) {
0432: success &= super .processOffsetGeneralLedgerPendingEntry(
0433: financialDocument, sequenceHelper,
0434: accountingLineCopy, explicitEntry, offsetEntry);
0435: } else {
0436: sequenceHelper.decrement(); // the parent already increments; b/c it assumes that all documents have offset entries all
0437: // of the time
0438: }
0439:
0440: return success;
0441: }
0442:
0443: /**
0444: * This method is responsible for iterating through all of the accounting lines in the document (source only) and checking to
0445: * see if they are all for the same account or not. It recognizes the first account element as the base, and then it iterates
0446: * through the rest. If it comes across one that doesn't match, then we know it's for multiple accounts.
0447: *
0448: * @param financialDocument submitted accounting document
0449: * @return true if multiple accounts are being used
0450: */
0451: private boolean isDocumentForMultipleAccounts(
0452: AccountingDocument financialDocument) {
0453: String baseAccountNumber = "";
0454:
0455: int index = 0;
0456: List<AccountingLine> lines = financialDocument
0457: .getSourceAccountingLines();
0458: for (AccountingLine line : lines) {
0459: if (index == 0) {
0460: baseAccountNumber = line.getAccountNumber();
0461: } else if (!baseAccountNumber.equals(line
0462: .getAccountNumber())) {
0463: return true;
0464: }
0465: index++;
0466: }
0467:
0468: return false;
0469: }
0470:
0471: /**
0472: * Offset entries are created for recodes (AVRC) always, so this method is one of 2 offsets that get created for an AVRC. Its
0473: * document type is set to DI. This uses the explicit entry as its model. In addition, an offset is generated for accruals
0474: * (AVAE) and adjustments (AVAD), but only if the document contains accounting lines for more than one account.
0475: *
0476: * @param financialDocument submitted accounting document
0477: * @param accountingLine accounting line from accounting document
0478: * @param explicitEntry represents explicit entry
0479: * @param offsetEntry represents offset entry
0480: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#customizeOffsetGeneralLedgerPendingEntry(org.kuali.core.document.FinancialDocument,
0481: * org.kuali.core.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry,
0482: * org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
0483: */
0484: protected boolean customizeOffsetGeneralLedgerPendingEntry(
0485: AccountingDocument financialDocument,
0486: AccountingLine accountingLine,
0487: GeneralLedgerPendingEntry explicitEntry,
0488: GeneralLedgerPendingEntry offsetEntry) {
0489: AuxiliaryVoucherDocument auxDoc = (AuxiliaryVoucherDocument) financialDocument;
0490:
0491: // set the document type to that of a Distrib. Of Income and Expense if it's a recode
0492: if (auxDoc.isRecodeType()) {
0493: offsetEntry
0494: .setFinancialDocumentTypeCode(SpringContext
0495: .getBean(DocumentTypeService.class)
0496: .getDocumentTypeCodeByClass(
0497: DistributionOfIncomeAndExpenseDocument.class));
0498:
0499: // set the posting period
0500: java.sql.Date today = SpringContext.getBean(
0501: DateTimeService.class).getCurrentSqlDateMidnight();
0502: offsetEntry.setUniversityFiscalPeriodCode(SpringContext
0503: .getBean(AccountingPeriodService.class).getByDate(
0504: today).getUniversityFiscalPeriodCode()); // use
0505: // current
0506: // period
0507: // code
0508: }
0509:
0510: // now set the offset entry to the specific offset object code for the AV generated offset fund balance; only if it's an
0511: // accrual or adjustment
0512: if (auxDoc.isAccrualType() || auxDoc.isAdjustmentType()) {
0513: String glpeOffsetObjectCode = SpringContext.getBean(
0514: ParameterService.class).getParameterValue(
0515: AuxiliaryVoucherDocument.class,
0516: getGeneralLedgerPendingEntryOffsetObjectCode());
0517: offsetEntry.setFinancialObjectCode(glpeOffsetObjectCode);
0518:
0519: // set the posting period
0520: offsetEntry.setUniversityFiscalPeriodCode(auxDoc
0521: .getPostingPeriodCode()); // use chosen posting period code
0522: }
0523:
0524: // set the reversal date to null
0525: offsetEntry.setFinancialDocumentReversalDate(null);
0526:
0527: // set the year to current
0528: offsetEntry.setUniversityFiscalYear(auxDoc.getPostingYear()); // use chosen posting year
0529:
0530: // although they are offsets, we need to set the offset indicator to false
0531: offsetEntry.setTransactionEntryOffsetIndicator(false);
0532:
0533: offsetEntry.refresh(); // may have changed foreign keys here; need accurate object code and account BOs at least
0534: offsetEntry.setAcctSufficientFundsFinObjCd(SpringContext
0535: .getBean(SufficientFundsService.class)
0536: .getSufficientFundsObjectCode(
0537: offsetEntry.getFinancialObject(),
0538: offsetEntry.getAccount()
0539: .getAccountSufficientFundsCode()));
0540:
0541: return true;
0542: }
0543:
0544: /**
0545: * This method creates an AV recode specific GLPE with a document type of DI. The sequence is managed outside of this method. It
0546: * uses the explicit entry as its model and then tweaks values appropriately.
0547: *
0548: * @param financialDocument submitted accounting document
0549: * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer
0550: * @param explicitEntry represents explicit entry
0551: * @return true if recode GLPE is added to the financial document successfully
0552: */
0553: private boolean processAuxiliaryVoucherRecodeDistributionOfIncomeAndExpenseGeneralLedgerPendingEntry(
0554: AccountingDocument financialDocument,
0555: GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
0556: GeneralLedgerPendingEntry explicitEntry) {
0557: // create a new instance based off of the explicit entry
0558: GeneralLedgerPendingEntry recodeGlpe = (GeneralLedgerPendingEntry) ObjectUtils
0559: .deepCopy(explicitEntry);
0560:
0561: // set the sequence number according to what was passed in - this is managed external to this method
0562: recodeGlpe.setTransactionLedgerEntrySequenceNumber(new Integer(
0563: sequenceHelper.getSequenceCounter()));
0564:
0565: // set the document type to that of a Distrib. Of Income and Expense
0566: recodeGlpe.setFinancialDocumentTypeCode(SpringContext.getBean(
0567: DocumentTypeService.class).getDocumentTypeCodeByClass(
0568: DistributionOfIncomeAndExpenseDocument.class));
0569:
0570: // set the object type code base on the value of the explicit entry
0571: recodeGlpe
0572: .setFinancialObjectTypeCode(getObjectTypeCodeForRecodeDistributionOfIncomeAndExpenseEntry(explicitEntry));
0573:
0574: // set the reversal date to null
0575: recodeGlpe.setFinancialDocumentReversalDate(null);
0576:
0577: // although this is an offsets, we need to set the offset indicator to false
0578: recodeGlpe.setTransactionEntryOffsetIndicator(false);
0579:
0580: // add the new recode offset entry to the document now
0581: financialDocument.getGeneralLedgerPendingEntries().add(
0582: recodeGlpe);
0583:
0584: return true;
0585: }
0586:
0587: /**
0588: * This method examines the accounting line passed in and returns the appropriate object type code. This rule converts specific
0589: * objects types from an object code on an accounting line to more general values. This is specific to the AV document.
0590: *
0591: * @param line accounting line where object type code is retrieved from
0592: * @return object type from a accounting line ((either financial object type code, financial object type not expenditure code,
0593: * or financial object type income not cash code))
0594: */
0595: protected String getObjectTypeCode(AccountingLine line) {
0596: Options options = SpringContext.getBean(OptionsService.class)
0597: .getCurrentYearOptions();
0598: String objectTypeCode = line.getObjectCode()
0599: .getFinancialObjectTypeCode();
0600:
0601: if (options.getFinObjTypeExpenditureexpCd().equals(
0602: objectTypeCode)
0603: || options.getFinObjTypeExpendNotExpCode().equals(
0604: objectTypeCode)) {
0605: objectTypeCode = options.getFinObjTypeExpNotExpendCode();
0606: } else if (options.getFinObjectTypeIncomecashCode().equals(
0607: objectTypeCode)
0608: || options.getFinObjTypeExpendNotExpCode().equals(
0609: objectTypeCode)) {
0610: objectTypeCode = options.getFinObjTypeIncomeNotCashCd();
0611: }
0612:
0613: return objectTypeCode;
0614: }
0615:
0616: /**
0617: * This method examines the explicit entry's object type and returns the appropriate object type code. This is specific to AV
0618: * recodes (AVRCs).
0619: *
0620: * @param explicitEntry
0621: * @return object type code from explicit entry (either financial object type code, financial object type expenditure code, or
0622: * financial object type income cash code)
0623: */
0624: protected String getObjectTypeCodeForRecodeDistributionOfIncomeAndExpenseEntry(
0625: GeneralLedgerPendingEntry explicitEntry) {
0626: Options options = SpringContext.getBean(OptionsService.class)
0627: .getCurrentYearOptions();
0628: String objectTypeCode = explicitEntry
0629: .getFinancialObjectTypeCode();
0630:
0631: if (options.getFinObjTypeExpNotExpendCode().equals(
0632: objectTypeCode)) {
0633: objectTypeCode = options.getFinObjTypeExpenditureexpCd();
0634: } else if (options.getFinObjTypeIncomeNotCashCd().equals(
0635: objectTypeCode)) {
0636: objectTypeCode = options.getFinObjectTypeIncomecashCode();
0637: }
0638:
0639: return objectTypeCode;
0640: }
0641:
0642: /**
0643: * Validates added accounting line. Override calls parent method and also checks whether or not the document and line have a
0644: * valid sub object and object level
0645: *
0646: * @param document submitted accounting document
0647: * @param accountingLine validated accounting line from accounting document
0648: * @return return true if accounting line is valid
0649: * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processCustomAddAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0650: * org.kuali.core.bo.AccountingLine)
0651: */
0652: @Override
0653: public boolean processCustomAddAccountingLineBusinessRules(
0654: AccountingDocument document, AccountingLine accountingLine) {
0655: boolean valid = super
0656: .processCustomAddAccountingLineBusinessRules(document,
0657: accountingLine);
0658:
0659: if (valid) {
0660: buildAccountingLineObjectType(accountingLine);
0661: valid &= isValidDocWithSubAndLevel(document, accountingLine);
0662: }
0663:
0664: return valid;
0665: }
0666:
0667: /**
0668: * Validates reviewed accounting line. Override calls parent method and also checks whether or not the document and line have a
0669: * valid sub object and object level
0670: *
0671: * @param document submitted accounting document
0672: * @param accountingLine validated accounting line from accounting document
0673: * @return return true if accounting line is valid
0674: * @see FinancialDocumentRuleBase#processCustomReviewAccountingLineBusinessRules(org.kuali.core.document.FinancialDocument,
0675: * org.kuali.core.bo.AccountingLine)
0676: */
0677: @Override
0678: public boolean processCustomReviewAccountingLineBusinessRules(
0679: AccountingDocument document, AccountingLine accountingLine) {
0680: boolean valid = true;
0681: valid &= super .processCustomReviewAccountingLineBusinessRules(
0682: document, accountingLine);
0683: if (valid) {
0684: buildAccountingLineObjectType(accountingLine);
0685: valid &= isValidDocWithSubAndLevel(document, accountingLine);
0686: }
0687: return valid;
0688: }
0689:
0690: /**
0691: * This method performs common validation for Transactional Document routes. Note the rule framework will handle validating all
0692: * of the accounting lines and also those checks that would normally be done on a save, automatically for us.<br/> <br/>
0693: * <code>{@link AuxiliaryVoucherDocument}</code> is different from other <code>{@link Document}</code> instances because it
0694: * requires all <code>{@link AccountingLine}</code> instances belong to the same Fund Group. That's done here by iterating
0695: * through the <code>{@link AccountingLine}</code> instances.
0696: *
0697: * @param document submitted document
0698: * @see FinancialDocumentRuleBase#processCustomRouteDocumentBusinessRules(Document)
0699: */
0700: @Override
0701: protected boolean processCustomRouteDocumentBusinessRules(
0702: Document document) {
0703: boolean valid = super
0704: .processCustomRouteDocumentBusinessRules(document);
0705: AuxiliaryVoucherDocument avDoc = (AuxiliaryVoucherDocument) document;
0706:
0707: if (valid) {
0708: valid = isPeriodAllowed(avDoc);
0709: }
0710:
0711: // make sure that a single chart is used for all accounting lines in the document
0712: if (valid) {
0713: valid = isSingleChartUsed((AccountingDocument) document);
0714: }
0715:
0716: // make sure that a single sub fund group is used for all accounting lines in the document
0717: if (valid) {
0718: valid = isSingleSubFundGroupUsed((AccountingDocument) document);
0719: }
0720:
0721: // make sure that a reversal date is entered for accruals
0722: if (valid) {
0723: valid = isValidReversalDate((AuxiliaryVoucherDocument) document);
0724: }
0725:
0726: return valid;
0727: }
0728:
0729: /**
0730: * Iterates <code>{@link AccountingLine}</code> instances in a given <code>{@link FinancialDocument}</code> instance and
0731: * compares them to see if they are all in the same Chart.
0732: *
0733: * @param document submitted document
0734: * @return true if only one chart of accounts code is used in a document's source accounting lines
0735: */
0736: protected boolean isSingleChartUsed(AccountingDocument document) {
0737: boolean valid = true;
0738:
0739: String baseChartCode = null;
0740: int index = 0;
0741:
0742: List<AccountingLine> lines = document
0743: .getSourceAccountingLines();
0744: for (AccountingLine line : lines) {
0745: if (index == 0) {
0746: baseChartCode = line.getChartOfAccountsCode();
0747: } else {
0748: String currentChartCode = line.getChartOfAccountsCode();
0749: if (!currentChartCode.equals(baseChartCode)) {
0750: reportError(ACCOUNTING_LINE_ERRORS,
0751: ERROR_DIFFERENT_CHARTS);
0752: return false;
0753: }
0754: }
0755: index++;
0756: }
0757: return true;
0758: }
0759:
0760: /**
0761: * Iterates <code>{@link AccountingLine}</code> instances in a given <code>{@link FinancialDocument}</code> instance and
0762: * compares them to see if they are all in the same Sub-Fund Group.
0763: *
0764: * @param document submitted document
0765: * @return true if only one sub fund group code is used in a document's source accounting lines
0766: */
0767: protected boolean isSingleSubFundGroupUsed(
0768: AccountingDocument document) {
0769: boolean valid = true;
0770:
0771: String baseSubFundGroupCode = null;
0772: int index = 0;
0773:
0774: List<AccountingLine> lines = document
0775: .getSourceAccountingLines();
0776: for (AccountingLine line : lines) {
0777: if (index == 0) {
0778: baseSubFundGroupCode = line.getAccount()
0779: .getSubFundGroupCode();
0780: } else {
0781: String currentSubFundGroup = line.getAccount()
0782: .getSubFundGroupCode();
0783: if (!currentSubFundGroup.equals(baseSubFundGroupCode)) {
0784: reportError(ACCOUNTING_LINE_ERRORS,
0785: ERROR_DIFFERENT_SUB_FUND_GROUPS);
0786: return false;
0787: }
0788: }
0789: index++;
0790: }
0791: return true;
0792: }
0793:
0794: /**
0795: * This method verifies that the user entered a reversal date, but only if it's an accrual.
0796: *
0797: * @param document submitted document
0798: * @return returns true if document is NOT an accrual type OR has a reversal date
0799: */
0800: protected boolean isValidReversalDate(
0801: AuxiliaryVoucherDocument document) {
0802: if (document.isAccrualType()
0803: && document.getReversalDate() == null) {
0804: reportError(REVERSAL_DATE,
0805: ERROR_INVALID_ACCRUAL_REVERSAL_DATE);
0806: return false;
0807: }
0808:
0809: return true;
0810: }
0811:
0812: /**
0813: * Overrides the parent implementation to sum all of the debit GLPEs up and sum all of the credit GLPEs up and then compare the
0814: * totals to each other, returning true if they are equal and false if they are not. The difference is that we ignore any DI
0815: * specific entries because while these are offsets, their offset indicators do not show this, so they would be counted in the
0816: * balancing and we don't want that. Added a check for simple balance between credit and debit values as entered on the
0817: * accountingLines, since that is also a requirement.
0818: *
0819: * @param financialDocument submitted accounting document
0820: * @return true if a document's accounting lines credit/debit lines are in balance and a document's non-DI credit and debit
0821: * GLPEs are also in balance
0822: */
0823: @Override
0824: protected boolean isDocumentBalanceValidConsideringDebitsAndCredits(
0825: AccountingDocument finanacialDocument) {
0826: AuxiliaryVoucherDocument avDoc = (AuxiliaryVoucherDocument) finanacialDocument;
0827:
0828: return accountingLinesBalance(avDoc) && glpesBalance(avDoc);
0829: }
0830:
0831: /**
0832: * Returns true if credit/debit entries are in balance
0833: *
0834: * @param avDoc submitted AuxiliaryVoucherDocument
0835: * @return true if the credit and debit entries from all accountingLines for the given document are in balance
0836: */
0837: private boolean accountingLinesBalance(
0838: AuxiliaryVoucherDocument avDoc) {
0839: KualiDecimal creditAmount = avDoc.getCreditTotal();
0840: KualiDecimal debitAmount = avDoc.getDebitTotal();
0841:
0842: boolean balanced = debitAmount.equals(creditAmount);
0843: if (!balanced) {
0844: String errorParams[] = { creditAmount.toString(),
0845: debitAmount.toString() };
0846: reportError(
0847: ACCOUNTING_LINE_ERRORS,
0848: ERROR_DOCUMENT_BALANCE_CONSIDERING_CREDIT_AND_DEBIT_AMOUNTS,
0849: errorParams);
0850: }
0851: return balanced;
0852: }
0853:
0854: /**
0855: * Returns true if the explicit, non-DI credit and debit GLPEs derived from the document's accountingLines are in balance
0856: *
0857: * @param avDoc submitted AuxiliaryVoucherDocument
0858: * @return true if the explicit, non-DI credit and debit GLPEs derived from the document's accountingLines are in balance
0859: */
0860: private boolean glpesBalance(AuxiliaryVoucherDocument avDoc) {
0861: // generate GLPEs specifically here so that we can compare debits to credits
0862: if (!SpringContext.getBean(
0863: GeneralLedgerPendingEntryService.class)
0864: .generateGeneralLedgerPendingEntries(avDoc)) {
0865: throw new ValidationException(
0866: "general ledger GLPE generation failed");
0867: }
0868:
0869: // now loop through all of the GLPEs and calculate buckets for debits and credits
0870: KualiDecimal creditAmount = new KualiDecimal(0);
0871: KualiDecimal debitAmount = new KualiDecimal(0);
0872:
0873: for (GeneralLedgerPendingEntry glpe : avDoc
0874: .getGeneralLedgerPendingEntries()) {
0875: // make sure we are looking at only the explicit entries that aren't DI types
0876: if (!glpe.isTransactionEntryOffsetIndicator()
0877: && !glpe
0878: .getFinancialDocumentTypeCode()
0879: .equals(
0880: SpringContext
0881: .getBean(
0882: DocumentTypeService.class)
0883: .getDocumentTypeCodeByClass(
0884: DistributionOfIncomeAndExpenseDocument.class))) {
0885: if (GL_CREDIT_CODE.equals(glpe
0886: .getTransactionDebitCreditCode())) {
0887: creditAmount = creditAmount.add(glpe
0888: .getTransactionLedgerEntryAmount());
0889: } else { // DEBIT
0890: debitAmount = debitAmount.add(glpe
0891: .getTransactionLedgerEntryAmount());
0892: }
0893: }
0894: }
0895:
0896: boolean balanced = debitAmount.equals(creditAmount);
0897: if (!balanced) {
0898: String errorParams[] = { creditAmount.toString(),
0899: debitAmount.toString() };
0900: reportError(ACCOUNTING_LINE_ERRORS, ERROR_DOCUMENT_BALANCE,
0901: errorParams);
0902: }
0903: return balanced;
0904: }
0905:
0906: /**
0907: * Fixes <code>{@link ObjectType}</code> for the given <code>{@link AccountingLine}</code> instance
0908: *
0909: * @param line accounting line
0910: */
0911: private void buildAccountingLineObjectType(AccountingLine line) {
0912: String objectTypeCode = line.getObjectCode()
0913: .getFinancialObjectTypeCode();
0914: line.setObjectTypeCode(objectTypeCode);
0915: line.refresh();
0916: }
0917:
0918: /**
0919: * This method checks to see if there is a valid combination of sub type and object level
0920: *
0921: * @param document submitted accounting document
0922: * @param accountingLine validated accounting line
0923: * @return return true if line contains a valid combination of object sub type and object level
0924: */
0925: private boolean isValidDocWithSubAndLevel(
0926: AccountingDocument document, AccountingLine accountingLine) {
0927: boolean retval = true;
0928:
0929: StringBuffer combinedCodes = new StringBuffer(accountingLine
0930: .getObjectType().getCode()).append(',').append(
0931: accountingLine.getObjectCode()
0932: .getFinancialObjectSubType().getCode()).append(
0933: ',').append(
0934: accountingLine.getObjectCode()
0935: .getFinancialObjectLevel()
0936: .getFinancialObjectLevelCode());
0937: ParameterEvaluator evalutator = SpringContext.getBean(
0938: ParameterService.class).getParameterEvaluator(
0939: AuxiliaryVoucherDocument.class,
0940: RESTRICTED_COMBINED_CODES);
0941:
0942: retval = !evalutator.equals(combinedCodes.toString());
0943:
0944: if (!retval) {
0945: String errorObjects[] = {
0946: accountingLine.getObjectCode()
0947: .getFinancialObjectCode(),
0948: accountingLine.getObjectCode()
0949: .getFinancialObjectLevel()
0950: .getFinancialObjectLevelCode(),
0951: accountingLine.getObjectCode()
0952: .getFinancialObjectSubType().getCode(),
0953: accountingLine.getObjectType().getCode() };
0954: reportError(
0955: ACCOUNTING_LINE_ERRORS,
0956: ERROR_DOCUMENT_INCORRECT_OBJ_CODE_WITH_SUB_TYPE_OBJ_LEVEL_AND_OBJ_TYPE,
0957: errorObjects);
0958: }
0959:
0960: return retval;
0961: }
0962:
0963: /**
0964: * This method determines if the posting period is valid for the document type.
0965: *
0966: * @param document submitted AuxiliaryVoucherDocument
0967: * @return true if it is a valid period for posting into
0968: */
0969: protected boolean isPeriodAllowed(AuxiliaryVoucherDocument document) {
0970: /*
0971: * Nota bene: a full summarization of these rules can be found in the comments to KULRNE-4634
0972: */
0973: // first we need to get the period itself to check these things
0974: boolean valid = true;
0975: AccountingPeriod acctPeriod = SpringContext.getBean(
0976: AccountingPeriodService.class).getByPeriod(
0977: document.getPostingPeriodCode(),
0978: document.getPostingYear());
0979:
0980: valid = SpringContext.getBean(ParameterService.class)
0981: .getParameterEvaluator(AuxiliaryVoucherDocument.class,
0982: RESTRICTED_PERIOD_CODES,
0983: document.getPostingPeriodCode())
0984: .evaluationSucceeds();
0985: if (!valid) {
0986: reportError(ACCOUNTING_PERIOD_STATUS_CODE_FIELD,
0987: ERROR_ACCOUNTING_PERIOD_OUT_OF_RANGE);
0988: }
0989:
0990: // can't post into a closed period
0991: if (acctPeriod == null
0992: || acctPeriod.getUniversityFiscalPeriodStatusCode()
0993: .equalsIgnoreCase(
0994: ACCOUNTING_PERIOD_STATUS_CLOSED)) {
0995: reportError(DOCUMENT_ERRORS,
0996: ERROR_DOCUMENT_ACCOUNTING_PERIOD_CLOSED);
0997: return false;
0998: }
0999:
1000: Timestamp ts = new Timestamp(new java.util.Date().getTime());
1001: AccountingPeriod currPeriod = SpringContext.getBean(
1002: AccountingPeriodService.class).getByDate(
1003: new Date(ts.getTime()));
1004:
1005: if (acctPeriod.getUniversityFiscalYear().equals(
1006: SpringContext.getBean(UniversityDateService.class)
1007: .getCurrentFiscalYear())) {
1008: if (SpringContext.getBean(AccountingPeriodService.class)
1009: .compareAccountingPeriodsByDate(acctPeriod,
1010: currPeriod) < 0) {
1011: // we've only got problems if the av's accounting period is earlier than now
1012:
1013: // are we in the grace period for this accounting period?
1014: if (!AuxiliaryVoucherDocumentRule
1015: .calculateIfWithinGracePeriod(new Date(ts
1016: .getTime()), acctPeriod)) {
1017: reportError(DOCUMENT_ERRORS,
1018: ERROR_DOCUMENT_ACCOUNTING_TWO_PERIODS);
1019: return false;
1020: }
1021: }
1022: } else {
1023: // it's not the same fiscal year, so we need to test whether we are currently
1024: // in the grace period of the acctPeriod
1025: if (!AuxiliaryVoucherDocumentRule
1026: .calculateIfWithinGracePeriod(
1027: new Date(ts.getTime()), acctPeriod)
1028: && AuxiliaryVoucherDocumentRule
1029: .isEndOfPreviousFiscalYear(acctPeriod)) {
1030: reportError(DOCUMENT_ERRORS,
1031: ERROR_DOCUMENT_ACCOUNTING_TWO_PERIODS);
1032: return false;
1033: }
1034: }
1035:
1036: boolean numericPeriod = true;
1037: Integer period = null;
1038: try {
1039: period = new Integer(document.getPostingPeriodCode());
1040: } catch (NumberFormatException nfe) {
1041: numericPeriod = false;
1042: }
1043: Integer year = document.getPostingYear();
1044:
1045: // check for specific posting issues
1046: if (document.isRecodeType()) {
1047: // can't post into a previous fiscal year
1048: Integer currFiscalYear = currPeriod
1049: .getUniversityFiscalYear();
1050: if (currFiscalYear > year) {
1051: reportError(DOCUMENT_ERRORS,
1052: ERROR_DOCUMENT_AV_INCORRECT_FISCAL_YEAR_AVRC);
1053: return false;
1054: }
1055: if (numericPeriod) {
1056: // check the posting period, throw out if period 13
1057: if (period > 12) {
1058: reportError(DOCUMENT_ERRORS,
1059: ERROR_DOCUMENT_AV_INCORRECT_POST_PERIOD_AVRC);
1060: return false;
1061: } else if (period < 1) {
1062: reportError(DOCUMENT_ERRORS,
1063: ERROR_ACCOUNTING_PERIOD_OUT_OF_RANGE);
1064: return false;
1065: }
1066: } else {
1067: // not a numeric period and this is a recode? Then we won't allow it; ref KULRNE-6001
1068: reportError(DOCUMENT_ERRORS,
1069: ERROR_DOCUMENT_AV_INCORRECT_POST_PERIOD_AVRC);
1070: return false;
1071: }
1072: }
1073: return valid;
1074: }
1075:
1076: /**
1077: * This method checks if a given moment of time is within an accounting period, or its auxiliary voucher grace period.
1078: *
1079: * @param today a date to check if it is within the period
1080: * @param periodToCheck the account period to check against
1081: * @return true if a given moment in time is within an accounting period or an auxiliary voucher grace period
1082: */
1083: public static boolean calculateIfWithinGracePeriod(Date today,
1084: AccountingPeriod periodToCheck) {
1085: boolean result = false;
1086: int todayAsComparableDate = AuxiliaryVoucherDocumentRule
1087: .comparableDateForm(today);
1088: int periodClose = new Integer(AuxiliaryVoucherDocumentRule
1089: .comparableDateForm(periodToCheck
1090: .getUniversityFiscalPeriodEndDate()));
1091: int periodBegin = AuxiliaryVoucherDocumentRule
1092: .comparableDateForm(AuxiliaryVoucherDocumentRule
1093: .calculateFirstDayOfMonth(periodToCheck
1094: .getUniversityFiscalPeriodEndDate()));
1095: int gracePeriodClose = periodClose
1096: + new Integer(
1097: SpringContext
1098: .getBean(ParameterService.class)
1099: .getParameterValue(
1100: AuxiliaryVoucherDocument.class,
1101: AUXILIARY_VOUCHER_ACCOUNTING_PERIOD_GRACE_PERIOD))
1102: .intValue();
1103: return (todayAsComparableDate >= periodBegin && todayAsComparableDate <= gracePeriodClose);
1104: }
1105:
1106: /**
1107: * This method returns a date as an approximate count of days since the BCE epoch.
1108: *
1109: * @param d the date to convert
1110: * @return an integer count of days, very approximate
1111: */
1112: public static int comparableDateForm(Date d) {
1113: java.util.Calendar cal = new java.util.GregorianCalendar();
1114: cal.setTime(d);
1115: return cal.get(java.util.Calendar.YEAR) * 365
1116: + cal.get(java.util.Calendar.DAY_OF_YEAR);
1117: }
1118:
1119: /**
1120: * Given a day, this method calculates what the first day of that month was.
1121: *
1122: * @param d date to find first of month for
1123: * @return date of the first day of the month
1124: */
1125: public static Date calculateFirstDayOfMonth(Date d) {
1126: java.util.Calendar cal = new java.util.GregorianCalendar();
1127: cal.setTime(d);
1128: int dayOfMonth = cal.get(java.util.Calendar.DAY_OF_MONTH) - 1;
1129: cal.add(java.util.Calendar.DAY_OF_YEAR, -1 * dayOfMonth);
1130: return new Date(cal.getTimeInMillis());
1131: }
1132:
1133: /**
1134: * This method checks if the given accounting period ends on the last day of the previous fiscal year
1135: *
1136: * @param acctPeriod accounting period to check
1137: * @return true if the accounting period ends with the fiscal year, false if otherwise
1138: */
1139: public static boolean isEndOfPreviousFiscalYear(
1140: AccountingPeriod acctPeriod) {
1141: UniversityDateService dateService = SpringContext
1142: .getBean(UniversityDateService.class);
1143: Date firstDayOfCurrFiscalYear = new Date(dateService
1144: .getFirstDateOfFiscalYear(
1145: dateService.getCurrentFiscalYear()).getTime());
1146: Date periodClose = acctPeriod
1147: .getUniversityFiscalPeriodEndDate();
1148: java.util.Calendar cal = new java.util.GregorianCalendar();
1149: cal.setTime(periodClose);
1150: cal.add(java.util.Calendar.DATE, 1);
1151: return (firstDayOfCurrFiscalYear.equals(new Date(cal
1152: .getTimeInMillis())));
1153: }
1154: }
|