001: /*
002: * Copyright 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.labor.rules;
017:
018: import static org.kuali.kfs.bo.AccountingLineOverride.CODE.EXPIRED_ACCOUNT;
019: import static org.kuali.kfs.bo.AccountingLineOverride.CODE.EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED;
020: import static org.kuali.kfs.bo.AccountingLineOverride.CODE.NON_FRINGE_ACCOUNT_USED;
021:
022: import java.util.ArrayList;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Set;
028: import java.util.Map.Entry;
029:
030: import org.apache.commons.lang.StringUtils;
031: import org.kuali.core.document.Document;
032: import org.kuali.core.service.BusinessObjectService;
033: import org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper;
034: import org.kuali.core.util.KualiDecimal;
035: import org.kuali.kfs.KFSConstants;
036: import org.kuali.kfs.KFSKeyConstants;
037: import org.kuali.kfs.KFSPropertyConstants;
038: import org.kuali.kfs.bo.AccountingLine;
039: import org.kuali.kfs.bo.Options;
040: import org.kuali.kfs.context.SpringContext;
041: import org.kuali.kfs.document.AccountingDocument;
042: import org.kuali.kfs.rules.AccountingDocumentRuleBase;
043: import org.kuali.kfs.service.OptionsService;
044: import org.kuali.module.chart.bo.Account;
045: import org.kuali.module.labor.LaborConstants;
046: import org.kuali.module.labor.LaborKeyConstants;
047: import org.kuali.module.labor.LaborPropertyConstants;
048: import org.kuali.module.labor.bo.ExpenseTransferAccountingLine;
049: import org.kuali.module.labor.bo.ExpenseTransferSourceAccountingLine;
050: import org.kuali.module.labor.bo.LedgerBalance;
051: import org.kuali.module.labor.document.LaborExpenseTransferDocumentBase;
052: import org.kuali.module.labor.document.LaborLedgerPostingDocument;
053: import org.kuali.module.labor.rule.GenerateLaborLedgerPendingEntriesRule;
054: import org.kuali.module.labor.util.ObjectUtil;
055:
056: /**
057: * Business rule(s) applicable to Labor Expense Transfer documents.
058: */
059: public class LaborExpenseTransferDocumentRules extends
060: AccountingDocumentRuleBase
061: implements
062: GenerateLaborLedgerPendingEntriesRule<LaborLedgerPostingDocument> {
063: /**
064: * Updates an accounting line
065: *
066: * @param accountingDocument document to be processed
067: * @param originalAccountingLine accounting line with old data
068: * @param updatedAccountingLine accounting line with the new data
069: * @return boolean
070: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#processCustomUpdateAccountingLineBusinessRules(org.kuali.kfs.document.AccountingDocument,
071: * org.kuali.kfs.bo.AccountingLine, org.kuali.kfs.bo.AccountingLine)
072: */
073: @Override
074: protected boolean processCustomUpdateAccountingLineBusinessRules(
075: AccountingDocument accountingDocument,
076: AccountingLine originalAccountingLine,
077: AccountingLine updatedAccountingLine) {
078: return processCustomAddAccountingLineBusinessRules(
079: accountingDocument, updatedAccountingLine);
080: }
081:
082: /**
083: * Adds an accounting line
084: *
085: * @param accountingDocument document to be processed
086: * @param originalAccountingLine accounting line with old data
087: * @param updatedAccountingLine accounting line with the new data
088: * @return boolean
089: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#processCustomAddAccountingLineBusinessRules(org.kuali.kfs.document.AccountingDocument,
090: * org.kuali.kfs.bo.AccountingLine)
091: */
092: @Override
093: protected boolean processCustomAddAccountingLineBusinessRules(
094: AccountingDocument accountingDocument,
095: AccountingLine accountingLine) {
096: boolean isValid = super
097: .processCustomAddAccountingLineBusinessRules(
098: accountingDocument, accountingLine);
099:
100: if (!isValid) {
101: return false;
102: }
103:
104: // not allow the duplicate source accounting line in the document
105: if (isDuplicateSourceAccountingLine(accountingDocument,
106: accountingLine)) {
107: reportError(
108: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
109: LaborKeyConstants.ERROR_DUPLICATE_SOURCE_ACCOUNTING_LINE);
110: return false;
111: }
112:
113: // determine if an expired account can be used to accept amount transfer
114: boolean canExpiredAccountBeUsed = canExpiredAccountBeUsed(accountingLine);
115: if (!canExpiredAccountBeUsed) {
116: reportError(KFSPropertyConstants.ACCOUNT,
117: KFSKeyConstants.ERROR_ACCOUNT_EXPIRED);
118: return false;
119: }
120:
121: // verify if the accounts in target accounting lines accept fringe benefits
122: if (!isAccountAcceptFringeBenefit(accountingLine)) {
123: reportError(KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
124: LaborKeyConstants.ERROR_ACCOUNT_NOT_ACCEPT_FRINGES,
125: accountingLine.getAccount()
126: .getReportsToChartOfAccountsCode(),
127: accountingLine.getAccount()
128: .getReportsToAccountNumber());
129: return false;
130: }
131:
132: return true;
133: }
134:
135: /**
136: * Route a document
137: *
138: * @param document the document to be routed
139: * @return boolean
140: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.Document)
141: */
142: @Override
143: protected boolean processCustomRouteDocumentBusinessRules(
144: Document document) {
145: LOG.info("started processCustomRouteDocumentBusinessRules");
146:
147: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) document;
148: List sourceLines = expenseTransferDocument
149: .getSourceAccountingLines();
150: List targetLines = expenseTransferDocument
151: .getTargetAccountingLines();
152:
153: boolean isValid = super
154: .processCustomRouteDocumentBusinessRules(document);
155:
156: // check to ensure totals of accounting lines in source and target sections match
157: isValid = isValid
158: & isAccountingLineTotalsMatch(sourceLines, targetLines);
159:
160: // check to ensure totals of accounting lines in source and target sections match by pay FY + pay period
161: isValid = isValid
162: & isAccountingLineTotalsMatchByPayFYAndPayPeriod(
163: sourceLines, targetLines);
164:
165: // allow a negative amount to be moved from one account to another but do not allow a negative amount to be created when the
166: // balance is positive
167: Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = this
168: .getAccountingLineGroupMap(sourceLines,
169: ExpenseTransferSourceAccountingLine.class);
170: if (isValid) {
171: boolean canNegtiveAmountBeTransferred = canNegtiveAmountBeTransferred(accountingLineGroupMap);
172: if (!canNegtiveAmountBeTransferred) {
173: reportError(
174: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
175: LaborKeyConstants.ERROR_CANNOT_TRANSFER_NEGATIVE_AMOUNT);
176: isValid = false;
177: }
178: }
179:
180: // target accounting lines must have the same amounts as source accounting lines for each object code
181: if (isValid) {
182: isValid = isValidAmountTransferredByObjectCode(expenseTransferDocument);
183: }
184:
185: // only allow a transfer of benefit dollars up to the amount that already exist in labor ledger detail for a given pay
186: // period
187: if (isValid) {
188: boolean isValidTransferAmount = isValidTransferAmount(accountingLineGroupMap);
189: if (!isValidTransferAmount) {
190: reportError(
191: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
192: LaborKeyConstants.ERROR_TRANSFER_AMOUNT_EXCEED_MAXIMUM);
193: isValid = false;
194: }
195: }
196:
197: return isValid;
198: }
199:
200: /**
201: * Determine whether the accounts in source/target accounting lines are valid
202: *
203: * @param accountingDocument the given accounting document
204: * @return true if the accounts in source/target accounting lines are valid; otherwise, false
205: */
206: private boolean isValidAccount(AccountingDocument accountingDocument) {
207: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
208:
209: for (Object sourceAccountingLine : expenseTransferDocument
210: .getSourceAccountingLines()) {
211: AccountingLine line = (AccountingLine) sourceAccountingLine;
212: if (line.getAccount() == null) {
213: reportError(
214: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
215: KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT,
216: new String[] { line.getChartOfAccountsCode(),
217: line.getAccountNumber() });
218: return false;
219: }
220: }
221:
222: for (Object targetAccountingLine : expenseTransferDocument
223: .getTargetAccountingLines()) {
224: AccountingLine line = (AccountingLine) targetAccountingLine;
225: if (line.getAccount() == null) {
226: reportError(
227: KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
228: KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT,
229: new String[] { line.getChartOfAccountsCode(),
230: line.getAccountNumber() });
231: return false;
232: }
233: }
234: return true;
235: }
236:
237: /**
238: * Performs validation on emplid
239: *
240: * @param emplid - id to validate
241: * @return boolean - true if id is valid, false if invalid
242: */
243: public boolean isValidEmplid(String emplid) {
244: boolean isValid = true;
245:
246: // verify id was given
247: if (StringUtils.isBlank(emplid)) {
248: reportError(LaborConstants.EMPLOYEE_LOOKUP_ERRORS,
249: LaborKeyConstants.MISSING_EMPLOYEE_ID, emplid);
250: isValid = false;
251: }
252:
253: return isValid;
254: }
255:
256: /**
257: * This method checks if the total sum amount of the source accounting line matches the total sum amount of the target
258: * accounting line, return true if the totals match, false otherwise.
259: *
260: * @param sourceLines
261: * @param targetLines
262: * @return
263: */
264: public boolean isAccountingLineTotalsMatch(List sourceLines,
265: List targetLines) {
266: boolean isValid = true;
267:
268: AccountingLine line = null;
269:
270: // totals for the from and to lines.
271: KualiDecimal sourceLinesAmount = new KualiDecimal(0);
272: KualiDecimal targetLinesAmount = new KualiDecimal(0);
273:
274: // sum source lines
275: for (Iterator i = sourceLines.iterator(); i.hasNext();) {
276: line = (ExpenseTransferAccountingLine) i.next();
277: sourceLinesAmount = sourceLinesAmount.add(line.getAmount());
278: }
279:
280: // sum target lines
281: for (Iterator i = targetLines.iterator(); i.hasNext();) {
282: line = (ExpenseTransferAccountingLine) i.next();
283: targetLinesAmount = targetLinesAmount.add(line.getAmount());
284: }
285:
286: // if totals don't match, then add error message
287: if (sourceLinesAmount.compareTo(targetLinesAmount) != 0) {
288: isValid = false;
289: reportError(
290: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
291: LaborKeyConstants.ACCOUNTING_LINE_TOTALS_MISMATCH_ERROR);
292: }
293:
294: return isValid;
295: }
296:
297: /**
298: * This method calls other methods to check if all source and target accounting lines match between each set by pay fiscal year
299: * and pay period, returning true if the totals match, false otherwise.
300: *
301: * @param sourceLines
302: * @param targetLines
303: * @return
304: */
305: protected boolean isAccountingLineTotalsMatchByPayFYAndPayPeriod(
306: List sourceLines, List targetLines) {
307: boolean isValid = true;
308:
309: // sum source lines by pay fy and pay period, store in map by key PayFY+PayPeriod
310: Map sourceLinesMap = sumAccountingLineAmountsByPayFYAndPayPeriod(sourceLines);
311:
312: // sum source lines by pay fy and pay period, store in map by key PayFY+PayPeriod
313: Map targetLinesMap = sumAccountingLineAmountsByPayFYAndPayPeriod(targetLines);
314:
315: // if totals don't match by PayFY+PayPeriod categories, then add error message
316: if (compareAccountingLineTotalsByPayFYAndPayPeriod(
317: sourceLinesMap, targetLinesMap) == false) {
318: isValid = false;
319: reportError(
320: KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
321: LaborKeyConstants.ACCOUNTING_LINE_TOTALS_BY_PAYFY_PAYPERIOD_MISMATCH_ERROR);
322: }
323:
324: return isValid;
325: }
326:
327: /**
328: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#isAmountValid(org.kuali.kfs.document.AccountingDocument,
329: * org.kuali.kfs.bo.AccountingLine)
330: */
331: @Override
332: public boolean isAmountValid(AccountingDocument document,
333: AccountingLine accountingLine) {
334: LOG.debug("started isAmountValid");
335:
336: KualiDecimal amount = accountingLine.getAmount();
337:
338: // Check for zero amount
339: if (amount.isZero()) {
340: reportError(KFSPropertyConstants.AMOUNT,
341: KFSKeyConstants.ERROR_ZERO_AMOUNT,
342: "an accounting line");
343: return false;
344: }
345: return true;
346: }
347:
348: /**
349: * determine whether the given accounting line has already been in the given document
350: *
351: * @param accountingDocument the given document
352: * @param accountingLine the given accounting line
353: * @return true if the given accounting line has already been in the given document; otherwise, false
354: */
355: protected boolean isDuplicateSourceAccountingLine(
356: AccountingDocument accountingDocument,
357: AccountingLine accountingLine) {
358: // only check source accounting lines
359: if (!(accountingLine instanceof ExpenseTransferSourceAccountingLine)) {
360: return false;
361: }
362:
363: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
364: List<ExpenseTransferSourceAccountingLine> sourceAccountingLines = expenseTransferDocument
365: .getSourceAccountingLines();
366: List<String> key = defaultKeyOfExpenseTransferAccountingLine();
367:
368: int counter = 0;
369: for (AccountingLine sourceAccountingLine : sourceAccountingLines) {
370: boolean isExisting = ObjectUtil.compareObject(
371: accountingLine, sourceAccountingLine, key);
372: counter = isExisting ? counter + 1 : counter;
373:
374: if (counter > 1) {
375: return true;
376: }
377: }
378: return false;
379: }
380:
381: /**
382: * determine whether the amount to be tranferred is only up to the amount in ledger balance for a given pay period
383: *
384: * @param accountingDocument the given accounting document
385: * @return true if the amount to be tranferred is only up to the amount in ledger balance for a given pay period; otherwise,
386: * false
387: */
388: protected boolean isValidTransferAmount(
389: Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap) {
390: Set<Entry<String, ExpenseTransferAccountingLine>> entrySet = accountingLineGroupMap
391: .entrySet();
392:
393: for (Entry<String, ExpenseTransferAccountingLine> entry : entrySet) {
394: ExpenseTransferAccountingLine accountingLine = entry
395: .getValue();
396: Map<String, Object> fieldValues = this
397: .buildFieldValueMap(accountingLine);
398:
399: KualiDecimal balanceAmount = getBalanceAmount(fieldValues,
400: accountingLine.getPayrollEndDateFiscalPeriodCode());
401: KualiDecimal transferAmount = accountingLine.getAmount();
402:
403: // the tranferred amount cannot greater than the balance amount
404: if (balanceAmount.abs().isLessThan(transferAmount.abs())) {
405: return false;
406: }
407:
408: // a positive amount cannot be transferred if the balance amount is negative
409: if (balanceAmount.isNegative()
410: && transferAmount.isPositive()) {
411: return false;
412: }
413: }
414: return true;
415: }
416:
417: /**
418: * Determine whether target accouting lines have the same amounts as source accounting lines for each object code
419: *
420: * @param accountingDocument the given accounting document
421: * @return true if target accouting lines have the same amounts as source accounting lines for each object code; otherwise,
422: * false
423: */
424: protected boolean isValidAmountTransferredByObjectCode(
425: AccountingDocument accountingDocument) {
426: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
427:
428: boolean isValid = true;
429:
430: Map<String, KualiDecimal> unbalancedObjectCodes = expenseTransferDocument
431: .getUnbalancedObjectCodes();
432: if (!unbalancedObjectCodes.isEmpty()) {
433: reportError(
434: KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
435: LaborKeyConstants.ERROR_TRANSFER_AMOUNT_NOT_BALANCED_BY_OBJECT);
436: isValid = false;
437: }
438:
439: return isValid;
440: }
441:
442: /**
443: * get the amount for a given period from a ledger balance that has the given values for specified fileds
444: *
445: * @param fieldValues the given fields and their values
446: * @param periodCode the given period
447: * @return the amount for a given period from the qualified ledger balance
448: */
449: protected KualiDecimal getBalanceAmount(
450: Map<String, Object> fieldValues, String periodCode) {
451: if (periodCode == null) {
452: return KualiDecimal.ZERO;
453: }
454:
455: fieldValues.put(
456: KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE,
457: KFSConstants.BALANCE_TYPE_ACTUAL);
458: KualiDecimal actualBalanceAmount = this
459: .getBalanceAmountOfGivenPeriod(fieldValues, periodCode);
460:
461: fieldValues.put(
462: KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE,
463: KFSConstants.BALANCE_TYPE_A21);
464: KualiDecimal effortBalanceAmount = this
465: .getBalanceAmountOfGivenPeriod(fieldValues, periodCode);
466:
467: return actualBalanceAmount.add(effortBalanceAmount);
468: }
469:
470: /**
471: * Gets the balance amount of a given period
472: *
473: * @param fieldValues
474: * @param periodCode
475: * @return
476: */
477: private KualiDecimal getBalanceAmountOfGivenPeriod(
478: Map<String, Object> fieldValues, String periodCode) {
479: KualiDecimal balanceAmount = KualiDecimal.ZERO;
480: List<LedgerBalance> ledgerBalances = (List<LedgerBalance>) SpringContext
481: .getBean(BusinessObjectService.class).findMatching(
482: LedgerBalance.class, fieldValues);
483: if (!ledgerBalances.isEmpty()) {
484: balanceAmount = ledgerBalances.get(0).getAmount(periodCode);
485: }
486: return balanceAmount;
487: }
488:
489: /**
490: * Determines whether a negtive amount can be transferred from one account to another
491: *
492: * @param accountingDocument the given accounting document
493: * @return true if a negtive amount can be transferred from one account to another; otherwise, false
494: */
495: protected boolean canNegtiveAmountBeTransferred(
496: Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap) {
497: for (String key : accountingLineGroupMap.keySet()) {
498: ExpenseTransferAccountingLine accountingLine = accountingLineGroupMap
499: .get(key);
500: Map<String, Object> fieldValues = this
501: .buildFieldValueMap(accountingLine);
502:
503: KualiDecimal balanceAmount = getBalanceAmount(fieldValues,
504: accountingLine.getPayrollEndDateFiscalPeriodCode());
505: KualiDecimal transferAmount = accountingLine.getAmount();
506:
507: // a negtive amount cannot be transferred if the balance amount is positive
508: if (transferAmount.isNegative()
509: && balanceAmount.isPositive()) {
510: return false;
511: }
512: }
513: return true;
514: }
515:
516: /**
517: * Groups the accounting lines by the specified key fields
518: *
519: * @param accountingLines the given accounting lines that are stored in a list
520: * @param clazz the class type of given accounting lines
521: * @return the accounting line groups
522: */
523: protected Map<String, ExpenseTransferAccountingLine> getAccountingLineGroupMap(
524: List<ExpenseTransferAccountingLine> accountingLines,
525: Class clazz) {
526: Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = new HashMap<String, ExpenseTransferAccountingLine>();
527:
528: for (ExpenseTransferAccountingLine accountingLine : accountingLines) {
529: String stringKey = ObjectUtil.buildPropertyMap(
530: accountingLine,
531: defaultKeyOfExpenseTransferAccountingLine())
532: .toString();
533: ExpenseTransferAccountingLine line = null;
534:
535: if (accountingLineGroupMap.containsKey(stringKey)) {
536: line = accountingLineGroupMap.get(stringKey);
537: KualiDecimal amount = line.getAmount();
538: line.setAmount(amount.add(accountingLine.getAmount()));
539: } else {
540: try {
541: line = (ExpenseTransferAccountingLine) clazz
542: .newInstance();
543: ObjectUtil.buildObject(line, accountingLine);
544: accountingLineGroupMap.put(stringKey, line);
545: } catch (Exception e) {
546: LOG
547: .error("Cannot create a new instance of ExpenseTransferAccountingLine"
548: + e);
549: }
550: }
551: }
552: return accountingLineGroupMap;
553: }
554:
555: /**
556: * Determines whether the account in the target line accepts fringe benefits.
557: *
558: * @param accountingLine the line to check
559: * @return true if the accounts in the target accounting lines accept fringe benefits; otherwise, false
560: */
561: protected boolean isAccountAcceptFringeBenefit(
562: AccountingLine accountingLine) {
563: boolean acceptsFringeBenefits = true;
564:
565: Account account = accountingLine.getAccount();
566: if (account != null
567: && !account.isAccountsFringesBnftIndicator()) {
568: String overrideCode = accountingLine.getOverrideCode();
569: boolean canNonFringeAccountUsed = NON_FRINGE_ACCOUNT_USED
570: .equals(overrideCode);
571: canNonFringeAccountUsed = canNonFringeAccountUsed
572: || EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED
573: .equals(overrideCode);
574:
575: if (!canNonFringeAccountUsed) {
576: acceptsFringeBenefits = false;
577: }
578: }
579:
580: return acceptsFringeBenefits;
581: }
582:
583: /**
584: * Gets the default key of ExpenseTransferAccountingLine
585: *
586: * @return the default key of ExpenseTransferAccountingLine
587: */
588: protected List<String> defaultKeyOfExpenseTransferAccountingLine() {
589: List<String> defaultKey = new ArrayList<String>();
590:
591: defaultKey.add(KFSPropertyConstants.POSTING_YEAR);
592: defaultKey.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
593: defaultKey.add(KFSPropertyConstants.ACCOUNT_NUMBER);
594: defaultKey.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER);
595:
596: defaultKey.add(KFSPropertyConstants.BALANCE_TYPE_CODE);
597: defaultKey.add(KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
598: defaultKey.add(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE);
599:
600: defaultKey.add(KFSPropertyConstants.EMPLID);
601: defaultKey.add(KFSPropertyConstants.POSITION_NUMBER);
602:
603: defaultKey
604: .add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_YEAR);
605: defaultKey
606: .add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_PERIOD_CODE);
607:
608: return defaultKey;
609: }
610:
611: /**
612: * This method returns a String that is a concatenation of pay fiscal year and pay period code.
613: *
614: * @param payFiscalYear
615: * @param payPeriodCode
616: * @return
617: */
618: private String createPayFYPeriodKey(Integer payFiscalYear,
619: String payPeriodCode) {
620:
621: StringBuffer payFYPeriodKey = new StringBuffer();
622:
623: payFYPeriodKey.append(payFiscalYear);
624: payFYPeriodKey.append(payPeriodCode);
625:
626: return payFYPeriodKey.toString();
627: }
628:
629: /**
630: * This method sums the totals of each accounting line, making an entry in a map for each unique pay fiscal year and pay period.
631: *
632: * @param accountingLines
633: * @return
634: */
635: private Map sumAccountingLineAmountsByPayFYAndPayPeriod(
636: List accountingLines) {
637:
638: ExpenseTransferAccountingLine line = null;
639: KualiDecimal linesAmount = new KualiDecimal(0);
640: Map linesMap = new HashMap();
641: String payFYPeriodKey = null;
642:
643: // go through source lines adding amounts to appropriate place in map
644: for (Iterator i = accountingLines.iterator(); i.hasNext();) {
645: // initialize
646: line = (ExpenseTransferAccountingLine) i.next();
647: linesAmount = new KualiDecimal(0);
648:
649: // create hash key
650: payFYPeriodKey = createPayFYPeriodKey(line
651: .getPayrollEndDateFiscalYear(), line
652: .getPayrollEndDateFiscalPeriodCode());
653:
654: // if entry exists, pull from hash
655: if (linesMap.containsKey(payFYPeriodKey)) {
656: linesAmount = (KualiDecimal) linesMap
657: .get(payFYPeriodKey);
658: }
659:
660: // update and store
661: linesAmount = linesAmount.add(line.getAmount());
662: linesMap.put(payFYPeriodKey, linesAmount);
663: }
664:
665: return linesMap;
666: }
667:
668: /**
669: * This method checks that the total amount of labor ledger accounting lines in the document's FROM section is equal to the
670: * total amount on the labor ledger accounting lines TO section for each unique combination of pay fiscal year and pay period. A
671: * value of true is returned if all amounts for each unique combination between source and target accounting lines match, false
672: * otherwise.
673: *
674: * @param sourceLinesMap
675: * @param targetLinesMap
676: * @return
677: */
678: private boolean compareAccountingLineTotalsByPayFYAndPayPeriod(
679: Map sourceLinesMap, Map targetLinesMap) {
680:
681: boolean isValid = true;
682: Map.Entry entry = null;
683: String currentKey = null;
684: KualiDecimal sourceLinesAmount = new KualiDecimal(0);
685: KualiDecimal targetLinesAmount = new KualiDecimal(0);
686:
687: // Loop through source lines comparing against target lines
688: for (Iterator i = sourceLinesMap.entrySet().iterator(); i
689: .hasNext()
690: && isValid;) {
691: // initialize
692: entry = (Map.Entry) i.next();
693: currentKey = (String) entry.getKey();
694: sourceLinesAmount = (KualiDecimal) entry.getValue();
695:
696: if (targetLinesMap.containsKey(currentKey)) {
697: targetLinesAmount = (KualiDecimal) targetLinesMap
698: .get(currentKey);
699:
700: // return false if the matching key values do not total each other
701: if (sourceLinesAmount.compareTo(targetLinesAmount) != 0) {
702: isValid = false;
703: }
704:
705: } else {
706: isValid = false;
707: }
708: }
709:
710: /*
711: * Now loop through target lines comparing against source lines. This finds missing entries from either direction (source or
712: * target)
713: */
714: for (Iterator i = targetLinesMap.entrySet().iterator(); i
715: .hasNext()
716: && isValid;) {
717: // initialize
718: entry = (Map.Entry) i.next();
719: currentKey = (String) entry.getKey();
720: targetLinesAmount = (KualiDecimal) entry.getValue();
721:
722: if (sourceLinesMap.containsKey(currentKey)) {
723: sourceLinesAmount = (KualiDecimal) sourceLinesMap
724: .get(currentKey);
725:
726: // return false if the matching key values do not total each other
727: if (targetLinesAmount.compareTo(sourceLinesAmount) != 0) {
728: isValid = false;
729: }
730:
731: } else {
732: isValid = false;
733: }
734: }
735: return isValid;
736: }
737:
738: /**
739: * Overriding hook into generate general ledger pending entries, so no GL pending entries are created.
740: *
741: * @see org.kuali.core.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.core.document.AccountingDocument,
742: * org.kuali.core.bo.AccountingLine, org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper)
743: */
744: @Override
745: public boolean processGenerateGeneralLedgerPendingEntries(
746: AccountingDocument accountingDocument,
747: AccountingLine accountingLine,
748: GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
749: return true;
750: }
751:
752: /**
753: * This method is the starting point for creating labor ledger pending entries. The logic used to create the LLPEs resides in
754: * this method.
755: *
756: * @param accountingDocument is an instance of <code>{@link LaborLedgerPostingDocument}</code>
757: * @param accountingLine
758: * @param sequenceHelper
759: * @return
760: */
761: public boolean processGenerateLaborLedgerPendingEntries(
762: LaborLedgerPostingDocument accountingDocument,
763: AccountingLine accountingLine,
764: GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
765: return true;
766: }
767:
768: /**
769: * @see org.kuali.kfs.rule.AccountingLineRule#isDebit(org.kuali.kfs.document.AccountingDocument,
770: * org.kuali.kfs.bo.AccountingLine)
771: */
772: public boolean isDebit(AccountingDocument financialDocument,
773: AccountingLine accountingLine) {
774: return false;
775: }
776:
777: /**
778: * determine whether the expired account in the target accounting line can be used.
779: *
780: * @param accountingDocument the given accounting line
781: * @return true if the expired account in the target accounting line can be used; otherwise, false
782: */
783: protected boolean canExpiredAccountBeUsed(
784: AccountingLine accountingLine) {
785: LOG.debug("started canExpiredAccountBeUsed(accountingLine)");
786:
787: Account account = accountingLine.getAccount();
788: if (account != null && account.isExpired()) {
789: String overrideCode = accountingLine.getOverrideCode();
790: boolean canExpiredAccountUsed = EXPIRED_ACCOUNT
791: .equals(overrideCode);
792: canExpiredAccountUsed = canExpiredAccountUsed
793: || EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED
794: .equals(overrideCode);
795:
796: if (!canExpiredAccountUsed) {
797: return false;
798: }
799: }
800: return true;
801: }
802:
803: /**
804: * build the field-value maps throught the given accouting line
805: *
806: * @param accountingLine the given accounting line
807: * @return the field-value maps built from the given accouting line
808: */
809: protected Map<String, Object> buildFieldValueMap(
810: ExpenseTransferAccountingLine accountingLine) {
811: Map<String, Object> fieldValues = new HashMap<String, Object>();
812:
813: fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
814: accountingLine.getPostingYear());
815: fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE,
816: accountingLine.getChartOfAccountsCode());
817: fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER,
818: accountingLine.getAccountNumber());
819:
820: String subAccountNumber = accountingLine.getSubAccountNumber();
821: subAccountNumber = StringUtils.isBlank(subAccountNumber) ? KFSConstants
822: .getDashSubAccountNumber()
823: : subAccountNumber;
824: fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER,
825: subAccountNumber);
826:
827: fieldValues.put(
828: KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE,
829: accountingLine.getBalanceTypeCode());
830: fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE,
831: accountingLine.getFinancialObjectCode());
832:
833: Options options = SpringContext.getBean(OptionsService.class)
834: .getOptions(accountingLine.getPostingYear());
835: fieldValues.put(
836: KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE,
837: options.getFinObjTypeExpenditureexpCd());
838:
839: String subObjectCode = accountingLine
840: .getFinancialSubObjectCode();
841: subObjectCode = StringUtils.isBlank(subObjectCode) ? KFSConstants
842: .getDashFinancialSubObjectCode()
843: : subObjectCode;
844: fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE,
845: subObjectCode);
846:
847: fieldValues.put(KFSPropertyConstants.EMPLID, accountingLine
848: .getEmplid());
849: fieldValues.put(KFSPropertyConstants.POSITION_NUMBER,
850: accountingLine.getPositionNumber());
851:
852: return fieldValues;
853: }
854: }
|