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 java.util.HashMap;
019: import java.util.List;
020: import java.util.Map;
021: import java.util.Set;
022: import java.util.Map.Entry;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.kuali.core.document.Document;
026: import org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper;
027: import org.kuali.core.util.GlobalVariables;
028: import org.kuali.core.util.KualiDecimal;
029: import org.kuali.kfs.KFSConstants;
030: import org.kuali.kfs.KFSPropertyConstants;
031: import org.kuali.kfs.bo.AccountingLine;
032: import org.kuali.kfs.context.SpringContext;
033: import org.kuali.kfs.document.AccountingDocument;
034: import org.kuali.kfs.service.ParameterService;
035: import org.kuali.module.labor.LaborConstants;
036: import org.kuali.module.labor.LaborKeyConstants;
037: import org.kuali.module.labor.LaborPropertyConstants;
038: import org.kuali.module.labor.bo.ExpenseTransferAccountingLine;
039: import org.kuali.module.labor.bo.ExpenseTransferSourceAccountingLine;
040: import org.kuali.module.labor.bo.ExpenseTransferTargetAccountingLine;
041: import org.kuali.module.labor.bo.LaborLedgerPendingEntry;
042: import org.kuali.module.labor.bo.LaborObject;
043: import org.kuali.module.labor.document.LaborExpenseTransferDocumentBase;
044: import org.kuali.module.labor.document.LaborLedgerPostingDocument;
045: import org.kuali.module.labor.document.SalaryExpenseTransferDocument;
046: import org.kuali.module.labor.rule.GenerateLaborLedgerBenefitClearingPendingEntriesRule;
047: import org.kuali.module.labor.service.LaborLedgerPendingEntryService;
048: import org.kuali.module.labor.util.LaborPendingEntryGenerator;
049:
050: /**
051: * Business rule(s) applicable to Salary Expense Transfer documents.
052: */
053: public class SalaryExpenseTransferDocumentRule extends
054: LaborExpenseTransferDocumentRules
055: implements
056: GenerateLaborLedgerBenefitClearingPendingEntriesRule<LaborLedgerPostingDocument> {
057:
058: /**
059: * Constructs a SalaryExpenseTransferDocumentRule.java.
060: */
061: public SalaryExpenseTransferDocumentRule() {
062: super ();
063: }
064:
065: /**
066: * Checks if user is allowed to edit the object code and check the object code balances match when document was inititated, else
067: * check they balance
068: *
069: * @param accountingDocument
070: * @return boolean
071: * @see org.kuali.module.labor.rules.LaborExpenseTransferDocumentRules#isValidAmountTransferredByObjectCode(org.kuali.kfs.document.AccountingDocument)
072: */
073: @Override
074: protected boolean isValidAmountTransferredByObjectCode(
075: AccountingDocument accountingDocument) {
076: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
077:
078: // check if user is allowed to edit the object code.
079: String adminGroupName = SpringContext
080: .getBean(ParameterService.class)
081: .getParameterValue(
082: SalaryExpenseTransferDocument.class,
083: LaborConstants.SalaryExpenseTransfer.SET_ADMIN_WORKGROUP_PARM_NM);
084: boolean isAdmin = false;
085: try {
086: isAdmin = GlobalVariables.getUserSession()
087: .getUniversalUser().isMember(adminGroupName);
088: } catch (Exception e) {
089: throw new RuntimeException(
090: "Workgroup "
091: + LaborConstants.SalaryExpenseTransfer.SET_ADMIN_WORKGROUP_PARM_NM
092: + " not found", e);
093: }
094:
095: if (isAdmin) {
096: return true;
097: }
098:
099: // if approving document, check the object code balances match when document was inititated, else check the balance
100: boolean isValid = true;
101: if (accountingDocument.getDocumentHeader()
102: .getWorkflowDocument().isApprovalRequested()) {
103: if (!isObjectCodeBalancesUnchanged(accountingDocument)) {
104: reportError(
105: KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
106: LaborKeyConstants.ERROR_TRANSFER_AMOUNT_BY_OBJECT_APPROVAL_CHANGE);
107: isValid = false;
108: }
109: } else {
110: if (!expenseTransferDocument.getUnbalancedObjectCodes()
111: .isEmpty()) {
112: reportError(
113: KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
114: LaborKeyConstants.ERROR_TRANSFER_AMOUNT_NOT_BALANCED_BY_OBJECT);
115: isValid = false;
116: }
117: }
118:
119: return isValid;
120: }
121:
122: /**
123: * Saves document
124: *
125: * @param document
126: * @return boolean
127: * @see org.kuali.core.rules.SaveDocumentRule#processCustomSaveDocumentBusinessRules(Document)
128: */
129: @Override
130: protected boolean processCustomSaveDocumentBusinessRules(
131: Document document) {
132: // Validate that an employee ID is enterred.
133: SalaryExpenseTransferDocument salaryExpenseTransferDocument = (SalaryExpenseTransferDocument) document;
134: String emplid = salaryExpenseTransferDocument.getEmplid();
135: if ((emplid == null) || (emplid.trim().length() == 0)) {
136: reportError(LaborConstants.EMPLOYEE_LOOKUP_ERRORS,
137: LaborKeyConstants.MISSING_EMPLOYEE_ID, emplid);
138: return false;
139: }
140:
141: // ensure the employee ids in the source accounting lines are same
142: AccountingDocument accountingDocument = (AccountingDocument) document;
143: if (!hasAccountingLinesSameEmployee(accountingDocument)) {
144: return false;
145: }
146:
147: return true;
148: }
149:
150: /**
151: * Adds an accounting line in the document and validates only salary labor object codes are allowed on the salary expense
152: * transfer document
153: *
154: * @param accountingDocument
155: * @param accountingLine
156: * @return boolean
157: * @see org.kuali.kfs.rules.AccountingDocumentRuleBase#processCustomAddAccountingLineBusinessRules(org.kuali.kfs.document.AccountingDocument,
158: * org.kuali.kfs.bo.AccountingLine)
159: */
160: @Override
161: protected boolean processCustomAddAccountingLineBusinessRules(
162: AccountingDocument accountingDocument,
163: AccountingLine accountingLine) {
164: boolean isValid = super
165: .processCustomAddAccountingLineBusinessRules(
166: accountingDocument, accountingLine);
167:
168: // only salary labor object codes are allowed on the salary expense transfer document
169: if (!isSalaryObjectCode(accountingLine)) {
170: reportError(KFSPropertyConstants.ACCOUNT,
171: LaborKeyConstants.INVALID_SALARY_OBJECT_CODE_ERROR,
172: accountingLine.getAccountNumber());
173: return false;
174: }
175:
176: return isValid;
177: }
178:
179: /**
180: * Process the routing of the document and validates that document must not have any pending labor ledger entries with same
181: * emplId, periodCode, accountNumber, objectCode
182: *
183: * @param document
184: * @return boolean
185: * @see org.kuali.module.labor.rules.LaborExpenseTransferDocumentRules#processCustomRouteDocumentBusinessRules(org.kuali.core.document.Document)
186: */
187: @Override
188: protected boolean processCustomRouteDocumentBusinessRules(
189: Document document) {
190: boolean isValid = super
191: .processCustomRouteDocumentBusinessRules(document);
192: SalaryExpenseTransferDocument expenseTransferDocument = (SalaryExpenseTransferDocument) document;
193:
194: // must not have any pending labor ledger entries with same emplId, periodCode, accountNumber, objectCode
195: if (isValid) {
196: isValid = !hasPendingLedgerEntry(expenseTransferDocument);
197: }
198:
199: return isValid;
200: }
201:
202: /**
203: * Determines whether the object code of given accounting line is a salary labor object code
204: *
205: * @param accountingLine the given accounting line
206: * @return true if the object code of given accounting line is a salary; otherwise, false
207: */
208: private boolean isSalaryObjectCode(AccountingLine accountingLine) {
209: ExpenseTransferAccountingLine expenseTransferAccountingLine = (ExpenseTransferAccountingLine) accountingLine;
210:
211: LaborObject laborObject = expenseTransferAccountingLine
212: .getLaborObject();
213: if (laborObject == null) {
214: return false;
215: }
216:
217: String fringeOrSalaryCode = laborObject
218: .getFinancialObjectFringeOrSalaryCode();
219: return StringUtils
220: .equals(
221: LaborConstants.SalaryExpenseTransfer.LABOR_LEDGER_SALARY_CODE,
222: fringeOrSalaryCode);
223: }
224:
225: /**
226: * Checks the current object code balance map of the document against the balances captured before the document was returned for
227: * approval.
228: *
229: * @param accountingDocument SalaryExpenseTransferDocument to check
230: * @return true if the balances have not changed, false if they have
231: */
232: private boolean isObjectCodeBalancesUnchanged(
233: AccountingDocument accountingDocument) {
234: boolean isUnchanged = true;
235:
236: Map<String, KualiDecimal> initiatedObjectCodeBalances = ((SalaryExpenseTransferDocument) accountingDocument)
237: .getApprovalObjectCodeBalances();
238: Map<String, KualiDecimal> currentObjectCodeBalances = ((SalaryExpenseTransferDocument) accountingDocument)
239: .getUnbalancedObjectCodes();
240:
241: Set<Entry<String, KualiDecimal>> initiatedObjectCodes = initiatedObjectCodeBalances
242: .entrySet();
243: Set<Entry<String, KualiDecimal>> currentObjectCodes = currentObjectCodeBalances
244: .entrySet();
245:
246: if (initiatedObjectCodes == null) {
247: if (currentObjectCodes != null) {
248: isUnchanged = false;
249: }
250: } else {
251: if (!initiatedObjectCodes.equals(currentObjectCodes)) {
252: isUnchanged = false;
253: }
254: }
255:
256: return isUnchanged;
257: }
258:
259: /**
260: * determine whether the employees in the source accouting lines are same
261: *
262: * @param accountingDocument the given accouting document
263: * @return true if the employees in the source accouting lines are same; otherwise, false
264: */
265: private boolean hasAccountingLinesSameEmployee(
266: AccountingDocument accountingDocument) {
267: LOG.debug("stared hasDocumentsSameEmployee");
268:
269: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
270: List<ExpenseTransferSourceAccountingLine> sourceAccountingLines = expenseTransferDocument
271: .getSourceAccountingLines();
272: List<ExpenseTransferTargetAccountingLine> targetAccountingLines = expenseTransferDocument
273: .getTargetAccountingLines();
274:
275: boolean sourceAccountingLinesValidationResult = true;
276: boolean targetAccountingLinesValidationResult = true;
277:
278: String employeeID = expenseTransferDocument.getEmplid();
279: String accountingLineEmplID = null;
280:
281: // Source Lines
282: for (ExpenseTransferSourceAccountingLine sourceAccountingLine : sourceAccountingLines) {
283: accountingLineEmplID = sourceAccountingLine.getEmplid();
284: if (accountingLineEmplID == null) {
285: sourceAccountingLinesValidationResult = false;
286: } else if (!employeeID.equals(accountingLineEmplID)) {
287: sourceAccountingLinesValidationResult = false;
288: }
289: }
290:
291: // Target lines
292: for (ExpenseTransferTargetAccountingLine targetAccountingLine : targetAccountingLines) {
293: accountingLineEmplID = targetAccountingLine.getEmplid();
294: if (accountingLineEmplID == null) {
295: targetAccountingLinesValidationResult = false;
296: } else if (!employeeID.equals(accountingLineEmplID)) {
297: targetAccountingLinesValidationResult = false;
298: }
299: }
300:
301: if (!sourceAccountingLinesValidationResult) {
302: reportError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES,
303: LaborKeyConstants.ERROR_EMPLOYEE_ID_NOT_SAME);
304: }
305:
306: if (!targetAccountingLinesValidationResult) {
307: reportError(
308: KFSPropertyConstants.TARGET_ACCOUNTING_LINES,
309: LaborKeyConstants.ERROR_EMPLOYEE_ID_NOT_SAME_IN_TARGET);
310: }
311:
312: return (sourceAccountingLinesValidationResult && targetAccountingLinesValidationResult);
313: }
314:
315: /**
316: * determine if there is any pending entry for the source accounting lines of the given document
317: *
318: * @param accountingDocument the given accounting document
319: * @return true if there is a pending entry for the source accounting lines of the given document; otherwise, false
320: */
321: public boolean hasPendingLedgerEntry(
322: AccountingDocument accountingDocument) {
323: LOG.info("started hasPendingLedgerEntry(accountingDocument)");
324:
325: LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) accountingDocument;
326: List<ExpenseTransferAccountingLine> sourceAccountingLines = expenseTransferDocument
327: .getSourceAccountingLines();
328:
329: Map<String, String> fieldValues = new HashMap<String, String>();
330: for (ExpenseTransferAccountingLine sourceAccountingLine : sourceAccountingLines) {
331: String payPeriodCode = sourceAccountingLine
332: .getPayrollEndDateFiscalPeriodCode();
333: String accountNumber = sourceAccountingLine
334: .getAccountNumber();
335: String objectCode = sourceAccountingLine
336: .getFinancialObjectCode();
337: String emplid = sourceAccountingLine.getEmplid();
338: String documentNumber = accountingDocument
339: .getDocumentNumber();
340:
341: fieldValues
342: .put(
343: LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_PERIOD_CODE,
344: payPeriodCode);
345: fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER,
346: accountNumber);
347: fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE,
348: objectCode);
349: fieldValues.put(KFSPropertyConstants.EMPLID, emplid);
350: fieldValues.put(KFSPropertyConstants.DOCUMENT_NUMBER,
351: KFSConstants.NOT_LOGICAL_OPERATOR + documentNumber);
352:
353: if (SpringContext.getBean(
354: LaborLedgerPendingEntryService.class)
355: .hasPendingLaborLedgerEntry(fieldValues)) {
356: reportError(
357: LaborConstants.EMPLOYEE_LOOKUP_ERRORS,
358: LaborKeyConstants.PENDING_SALARY_TRANSFER_ERROR,
359: emplid, payPeriodCode, accountNumber,
360: objectCode);
361: return true;
362: }
363: }
364:
365: return false;
366: }
367:
368: /**
369: * @param LaborLedgerPostingDocument the given labor ledger accounting document
370: * @return true after creating a list of Expense Pending entries and Benefit pending Entries
371: * @see org.kuali.module.labor.rules.LaborExpenseTransferDocumentRules#processGenerateLaborLedgerPendingEntries(org.kuali.module.labor.document.LaborLedgerPostingDocument,
372: * org.kuali.module.labor.bo.ExpenseTransferAccountingLine, org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper)
373: */
374: @Override
375: public boolean processGenerateLaborLedgerPendingEntries(
376: LaborLedgerPostingDocument document,
377: AccountingLine accountingLine,
378: GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
379: LOG.info("started processGenerateLaborLedgerPendingEntries()");
380:
381: ExpenseTransferAccountingLine expenseTransferAccountingLine = (ExpenseTransferAccountingLine) accountingLine;
382:
383: List<LaborLedgerPendingEntry> expensePendingEntries = LaborPendingEntryGenerator
384: .generateExpensePendingEntries(document,
385: expenseTransferAccountingLine, sequenceHelper);
386: document.getLaborLedgerPendingEntries().addAll(
387: expensePendingEntries);
388:
389: List<LaborLedgerPendingEntry> benefitPendingEntries = LaborPendingEntryGenerator
390: .generateBenefitPendingEntries(document,
391: expenseTransferAccountingLine, sequenceHelper);
392: document.getLaborLedgerPendingEntries().addAll(
393: benefitPendingEntries);
394:
395: return true;
396: }
397:
398: /**
399: * @param LaborLedgerPostingDocument the given labor ledger accounting document
400: * @return true after generate Benefit Clearing Pending Entries for the document
401: * @see org.kuali.module.labor.rule.GenerateLaborLedgerBenefitClearingPendingEntriesRule#processGenerateLaborLedgerBenefitClearingPendingEntries(org.kuali.kfs.document.AccountingDocument,
402: * org.kuali.core.util.GeneralLedgerPendingEntrySequenceHelper)
403: */
404: public boolean processGenerateLaborLedgerBenefitClearingPendingEntries(
405: LaborLedgerPostingDocument document,
406: GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
407: LOG
408: .info("started processGenerateLaborLedgerBenefitClearingPendingEntries()");
409:
410: String chartOfAccountsCode = SpringContext
411: .getBean(ParameterService.class)
412: .getParameterValue(
413: SalaryExpenseTransferDocument.class,
414: LaborConstants.SalaryExpenseTransfer.BENEFIT_CLEARING_CHART_PARM_NM);
415: String accountNumber = SpringContext
416: .getBean(ParameterService.class)
417: .getParameterValue(
418: SalaryExpenseTransferDocument.class,
419: LaborConstants.SalaryExpenseTransfer.BENEFIT_CLEARING_ACCOUNT_PARM_NM);
420:
421: List<LaborLedgerPendingEntry> benefitClearingPendingEntries = LaborPendingEntryGenerator
422: .generateBenefitClearingPendingEntries(document,
423: sequenceHelper, accountNumber,
424: chartOfAccountsCode);
425: document.getLaborLedgerPendingEntries().addAll(
426: benefitClearingPendingEntries);
427:
428: return true;
429: }
430: }
|