001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.module.financial.rules;
017:
018: import static org.kuali.kfs.KFSConstants.BALANCE_TYPE_ACTUAL;
019:
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.kuali.core.util.GlobalVariables;
026: import org.kuali.core.util.KualiDecimal;
027: import org.kuali.kfs.KFSKeyConstants;
028: import org.kuali.kfs.bo.AccountingLine;
029: import org.kuali.kfs.bo.GeneralLedgerPendingEntry;
030: import org.kuali.kfs.bo.Options;
031: import org.kuali.kfs.context.SpringContext;
032: import org.kuali.kfs.document.AccountingDocument;
033: import org.kuali.kfs.rules.AccountingDocumentRuleBase;
034: import org.kuali.kfs.rules.AccountingDocumentRuleUtil;
035: import org.kuali.kfs.service.OptionsService;
036: import org.kuali.module.financial.document.TransferOfFundsDocument;
037:
038: /**
039: * Business rule(s) applicable to Transfer of Funds documents.
040: */
041: public class TransferOfFundsDocumentRule extends
042: AccountingDocumentRuleBase implements
043: TransferOfFundsDocumentRuleConstants {
044:
045: /**
046: * Set attributes of an offset pending entry according to rules specific to TransferOfFundsDocument. The current rules
047: * require setting the balance type code to 'actual'.
048: *
049: * @param financialDocument The accounting document containing the general ledger pending entries being customized.
050: * @param accountingLine The accounting line the explicit general ledger pending entry was generated from.
051: * @param explicitEntry The explicit general ledger pending entry the offset entry is generated for.
052: * @param offsetEntry The offset general ledger pending entry being customized.
053: * @return This method always returns true.
054: *
055: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#customizeOffsetGeneralLedgerPendingEntry(org.kuali.core.document.FinancialDocument,
056: * org.kuali.core.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry,
057: * org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
058: */
059: @Override
060: protected boolean customizeOffsetGeneralLedgerPendingEntry(
061: AccountingDocument financialDocument,
062: AccountingLine accountingLine,
063: GeneralLedgerPendingEntry explicitEntry,
064: GeneralLedgerPendingEntry offsetEntry) {
065: offsetEntry.setFinancialBalanceTypeCode(BALANCE_TYPE_ACTUAL);
066: return true;
067: }
068:
069: /**
070: * Set attributes of an explicit pending entry according to rules specific to TransferOfFundsDocument.
071: *
072: * @param financialDocument The accounting document containing the general ledger pending entries being customized.
073: * @param accountingLine The accounting line the explicit general ledger pending entry was generated from.
074: * @param explicitEntry The explicit general ledger pending entry to be customized.
075: *
076: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.core.document.FinancialDocument,
077: * org.kuali.core.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
078: */
079: @Override
080: protected void customizeExplicitGeneralLedgerPendingEntry(
081: AccountingDocument financialDocument,
082: AccountingLine accountingLine,
083: GeneralLedgerPendingEntry explicitEntry) {
084: Options options = SpringContext.getBean(OptionsService.class)
085: .getCurrentYearOptions();
086:
087: explicitEntry.setFinancialBalanceTypeCode(BALANCE_TYPE_ACTUAL);
088: if (isExpense(accountingLine)) {
089: explicitEntry.setFinancialObjectTypeCode(options
090: .getFinancialObjectTypeTransferExpenseCd());
091: } else {
092: if (isIncome(accountingLine)) {
093: explicitEntry.setFinancialObjectTypeCode(options
094: .getFinancialObjectTypeTransferIncomeCd());
095: } else {
096: explicitEntry
097: .setFinancialObjectTypeCode(AccountingDocumentRuleUtil
098: .getObjectCodeTypeCodeWithoutSideEffects(accountingLine));
099: }
100: }
101: }
102:
103: /**
104: * Adds the following restrictions in addition to those provided by <code>IsDebitUtils.isDebitConsideringNothingPositiveOnly</code>
105: * <ol>
106: * <li> Only allow income or expense object type codes
107: * <li> Target lines have the opposite debit/credit codes as the source lines
108: * </ol>
109: *
110: * @param financialDocument The document used to determine if the accounting line is a debit line.
111: * @param accountingLine The accounting line to be analyzed.
112: * @return True if the accounting line provided is a debit line, false otherwise.
113: *
114: * @see IsDebitUtils#isDebitConsideringNothingPositiveOnly(FinancialDocumentRuleBase, FinancialDocument, AccountingLine)
115: * @see org.kuali.core.rule.AccountingLineRule#isDebit(org.kuali.core.document.FinancialDocument,
116: * org.kuali.core.bo.AccountingLine)
117: */
118: public boolean isDebit(AccountingDocument financialDocument,
119: AccountingLine accountingLine) {
120: // only allow income or expense
121: if (!isIncome(accountingLine) && !isExpense(accountingLine)) {
122: throw new IllegalStateException(
123: IsDebitUtils.isDebitCalculationIllegalStateExceptionMessage);
124: }
125: boolean isDebit = false;
126: if (accountingLine.isSourceAccountingLine()) {
127: isDebit = IsDebitUtils
128: .isDebitConsideringNothingPositiveOnly(this ,
129: financialDocument, accountingLine);
130: } else if (accountingLine.isTargetAccountingLine()) {
131: isDebit = !IsDebitUtils
132: .isDebitConsideringNothingPositiveOnly(this ,
133: financialDocument, accountingLine);
134: } else {
135: throw new IllegalStateException(
136: IsDebitUtils.isInvalidLineTypeIllegalArgumentExceptionMessage);
137: }
138:
139: return isDebit;
140: }
141:
142: /**
143: * Overrides to check balances across mandatory transfers and non-mandatory transfers. Also checks balances across fund groups.
144: *
145: * @param financialDocument The document to retrieve the balance from and validate against.
146: * @return True if the document balance is valid based on a collection of validation checks performed, false otherwise.
147: *
148: * @see FinancialDocumentRuleBase#isDocumentBalanceValid(FinancialDocument)
149: */
150: @Override
151: protected boolean isDocumentBalanceValid(
152: AccountingDocument financialDocument) {
153: boolean isValid = super
154: .isDocumentBalanceValid(financialDocument);
155:
156: TransferOfFundsDocument tofDoc = (TransferOfFundsDocument) financialDocument;
157: // make sure accounting lines balance across mandatory and non-mandatory transfers
158: if (isValid) {
159: isValid = isMandatoryTransferTotalAndNonMandatoryTransferTotalBalanceValid(tofDoc);
160: }
161:
162: // make sure accounting lines for a TOF balance across agency and clearing fund groups - IU specific
163: if (isValid) {
164: isValid = isFundGroupsBalanceValid(tofDoc);
165: }
166:
167: return isValid;
168: }
169:
170: /**
171: * This is a helper method that wraps the fund group balancing check. This check can be configured by updating the
172: * application parameter table that is associated with this check. See the document's specification for details.
173: *
174: * @param tofDoc The transfer of funds document the fund groups will be pulled from and validated.
175: * @return True if the fund group balance if valid, false otherwise.
176: *
177: * @see #isFundGroupSetBalanceValid(TransferOfFundsDocument)
178: */
179: private boolean isFundGroupsBalanceValid(
180: TransferOfFundsDocument tofDoc) {
181: return isFundGroupSetBalanceValid(tofDoc,
182: TransferOfFundsDocument.class,
183: APPLICATION_PARAMETER.FUND_GROUP_BALANCING_SET);
184: }
185:
186: /**
187: * This method checks the sum of all of the "From" accounting lines with mandatory transfer object codes against the sum of all
188: * of the "To" accounting lines with mandatory transfer object codes. In addition, it does the same, but for accounting lines
189: * with non-mandatory transfer object code. This is to enforce the rule that the document must balance within the object code
190: * object sub-type codes of mandatory transfers and non-mandatory transfers.
191: *
192: * @param tofDoc The transfer of funds document to be validated.
193: * @return True if they balance; false otherwise.
194: */
195: private boolean isMandatoryTransferTotalAndNonMandatoryTransferTotalBalanceValid(
196: TransferOfFundsDocument tofDoc) {
197: List lines = new ArrayList();
198:
199: lines.addAll(tofDoc.getSourceAccountingLines());
200: lines.addAll(tofDoc.getTargetAccountingLines());
201:
202: // sum the from lines.
203: KualiDecimal mandatoryTransferFromAmount = new KualiDecimal(0);
204: KualiDecimal nonMandatoryTransferFromAmount = new KualiDecimal(
205: 0);
206: KualiDecimal mandatoryTransferToAmount = new KualiDecimal(0);
207: KualiDecimal nonMandatoryTransferToAmount = new KualiDecimal(0);
208:
209: for (Iterator i = lines.iterator(); i.hasNext();) {
210: AccountingLine line = (AccountingLine) i.next();
211: String objectSubTypeCode = line.getObjectCode()
212: .getFinancialObjectSubTypeCode();
213:
214: if (isNonMandatoryTransfersSubType(objectSubTypeCode)) {
215: if (line.isSourceAccountingLine()) {
216: nonMandatoryTransferFromAmount = nonMandatoryTransferFromAmount
217: .add(line.getAmount());
218: } else {
219: nonMandatoryTransferToAmount = nonMandatoryTransferToAmount
220: .add(line.getAmount());
221: }
222: } else if (isMandatoryTransfersSubType(objectSubTypeCode)) {
223: if (line.isSourceAccountingLine()) {
224: mandatoryTransferFromAmount = mandatoryTransferFromAmount
225: .add(line.getAmount());
226: } else {
227: mandatoryTransferToAmount = mandatoryTransferToAmount
228: .add(line.getAmount());
229: }
230: }
231: }
232:
233: // check that the amounts balance across mandatory transfers and non-mandatory transfers
234: boolean isValid = true;
235:
236: if (mandatoryTransferFromAmount
237: .compareTo(mandatoryTransferToAmount) != 0) {
238: isValid = false;
239: GlobalVariables
240: .getErrorMap()
241: .putError(
242: "document.sourceAccountingLines",
243: KFSKeyConstants.ERROR_DOCUMENT_TOF_MANDATORY_TRANSFERS_DO_NOT_BALANCE);
244: }
245:
246: if (nonMandatoryTransferFromAmount
247: .compareTo(nonMandatoryTransferToAmount) != 0) {
248: isValid = false;
249: GlobalVariables
250: .getErrorMap()
251: .putError(
252: "document.sourceAccountingLines",
253: KFSKeyConstants.ERROR_DOCUMENT_TOF_NON_MANDATORY_TRANSFERS_DO_NOT_BALANCE);
254: }
255:
256: return isValid;
257: }
258:
259: /**
260: * Overrides the parent to make sure that the chosen object code's object sub-type code is either Mandatory Transfer or
261: * Non-Mandatory Transfer. This is called by the parent's processAddAccountingLine() method.
262: *
263: * @param documentClass A value required to override this method, but one that is not used in this class, so null can be passed.
264: * @param accountingLine The accounting line the object code will be retrieved from for validation.
265: * @return True if the object code's object sub-type code is a mandatory or non-mandatory transfer; false otherwise.
266: *
267: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#isObjectSubTypeAllowed(Class, org.kuali.core.bo.AccountingLine)
268: */
269: @Override
270: public boolean isObjectSubTypeAllowed(Class documentClass,
271: AccountingLine accountingLine) {
272: accountingLine.refreshReferenceObject("objectCode");
273: String objectSubTypeCode = accountingLine.getObjectCode()
274: .getFinancialObjectSubTypeCode();
275:
276: // make sure a object sub type code exists for this object code
277: if (StringUtils.isBlank(objectSubTypeCode)) {
278: GlobalVariables
279: .getErrorMap()
280: .putError(
281: "financialObjectCode",
282: KFSKeyConstants.ERROR_DOCUMENT_TOF_OBJECT_SUB_TYPE_IS_NULL,
283: accountingLine.getFinancialObjectCode());
284: return false;
285: }
286:
287: if (!isMandatoryTransfersSubType(objectSubTypeCode)
288: && !isNonMandatoryTransfersSubType(objectSubTypeCode)) {
289: GlobalVariables
290: .getErrorMap()
291: .putError(
292: "financialObjectCode",
293: KFSKeyConstants.ERROR_DOCUMENT_TOF_OBJECT_SUB_TYPE_NOT_MANDATORY_OR_NON_MANDATORY_TRANSFER,
294: new String[] {
295: accountingLine
296: .getObjectCode()
297: .getFinancialObjectSubType()
298: .getFinancialObjectSubTypeName(),
299: accountingLine
300: .getFinancialObjectCode() });
301: return false;
302: }
303:
304: return true;
305: }
306:
307: /**
308: * Overrides the parent to make sure that the chosen object code's object code is Income/Expense.
309: *
310: * @param accountingLine The accounting line the object code will be retrieved from and validated.
311: * @return True if the object code is income or expense, otherwise false.
312: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#isObjectCodeAllowed(Class, org.kuali.core.bo.AccountingLine)
313: */
314: @Override
315: public boolean isObjectCodeAllowed(Class documentClass,
316: AccountingLine accountingLine) {
317: boolean isObjectCodeAllowed = super .isObjectCodeAllowed(
318: documentClass, accountingLine);
319:
320: if (!isIncome(accountingLine) && !isExpense(accountingLine)) {
321: GlobalVariables
322: .getErrorMap()
323: .putError(
324: "financialObjectCode",
325: KFSKeyConstants.ERROR_DOCUMENT_TOF_INVALID_OBJECT_TYPE_CODES,
326: new String[] {
327: accountingLine
328: .getObjectCode()
329: .getFinancialObjectTypeCode(),
330: accountingLine
331: .getObjectCode()
332: .getFinancialObjectSubTypeCode() });
333: isObjectCodeAllowed = false;
334: }
335:
336: return isObjectCodeAllowed;
337: }
338: }
|