/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ld.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.module.ld.LaborConstants; import org.kuali.kfs.module.ld.LaborPropertyConstants; import org.kuali.kfs.module.ld.businessobject.BenefitsCalculation; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferTargetAccountingLine; import org.kuali.kfs.module.ld.businessobject.LaborLedgerPendingEntry; import org.kuali.kfs.module.ld.businessobject.PositionObjectBenefit; import org.kuali.kfs.module.ld.document.LaborLedgerPostingDocument; import org.kuali.kfs.module.ld.document.service.LaborPendingEntryConverterService; import org.kuali.kfs.module.ld.service.LaborBenefitsCalculationService; import org.kuali.kfs.module.ld.service.LaborPositionObjectBenefitService; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.ObjectUtils; /** * This class is used to help generating pending entries for the given labor documents */ public class LaborPendingEntryGenerator { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborPendingEntryGenerator.class); /** * generate the expense pending entries based on the given document and accounting line * * @param document the given accounting document * @param accountingLine the given accounting line * @param sequenceHelper the given sequence helper * @return a set of expense pending entries */ public static List<LaborLedgerPendingEntry> generateExpensePendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) { List<LaborLedgerPendingEntry> expensePendingEntries = new ArrayList<LaborLedgerPendingEntry>(); LaborLedgerPendingEntry expensePendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpensePendingEntry(document, accountingLine, sequenceHelper); expensePendingEntries.add(expensePendingEntry); // KFSMI-6863: always create A2 entries regardless of fiscal period LaborLedgerPendingEntry expenseA21PendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpenseA21PendingEntry(document, accountingLine, sequenceHelper); expensePendingEntries.add(expenseA21PendingEntry); LaborLedgerPendingEntry expenseA21ReversalPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpenseA21ReversalPendingEntry(document, accountingLine, sequenceHelper); expensePendingEntries.add(expenseA21ReversalPendingEntry); //refresh nonupdateable references for financial object... refreshObjectCodeNonUpdateableReferences(expensePendingEntries); return expensePendingEntries; } /** * generate the benefit pending entries based on the given document and accounting line * * @param document the given accounting document * @param accountingLine the given accounting line * @param sequenceHelper the given sequence helper * @return a set of benefit pending entries */ public static List<LaborLedgerPendingEntry> generateBenefitPendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) { accountingLine.refreshReferenceObject(KFSPropertyConstants.LABOR_OBJECT); if (ObjectUtils.isNull(accountingLine.getLaborObject())) { return null; } String FringeOrSalaryCode = accountingLine.getLaborObject().getFinancialObjectFringeOrSalaryCode(); if (!LaborConstants.SalaryExpenseTransfer.LABOR_LEDGER_SALARY_CODE.equals(FringeOrSalaryCode)) { return null; } Integer payrollFiscalyear = accountingLine.getPayrollEndDateFiscalYear(); String chartOfAccountsCode = accountingLine.getChartOfAccountsCode(); String objectCode = accountingLine.getFinancialObjectCode(); Collection<PositionObjectBenefit> positionObjectBenefits = SpringContext.getBean(LaborPositionObjectBenefitService.class).getActivePositionObjectBenefits(payrollFiscalyear, chartOfAccountsCode, objectCode); List<LaborLedgerPendingEntry> benefitPendingEntries = new ArrayList<LaborLedgerPendingEntry>(); for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) { positionObjectBenefit.setLaborBenefitRateCategoryCode(accountingLine.getAccount().getLaborBenefitRateCategoryCode()); String fringeBenefitObjectCode = positionObjectBenefit.getBenefitsCalculation().getPositionFringeBenefitObjectCode(); KualiDecimal benefitAmount = SpringContext.getBean(LaborBenefitsCalculationService.class).calculateFringeBenefit(positionObjectBenefit, accountingLine.getAmount(), accountingLine.getAccountNumber(), accountingLine.getSubAccountNumber()); if (benefitAmount.isNonZero() && positionObjectBenefit.getBenefitsCalculation().isActive()) { ParameterService parameterService = SpringContext.getBean(ParameterService.class); Boolean enableFringeBenefitCalculationByBenefitRate = parameterService.getParameterValueAsBoolean(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, LaborConstants.BenefitCalculation.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_PARAMETER); //If fringeBenefitObjectCode is empty and its enable to use calculation by benefit rate if(StringUtils.isEmpty(fringeBenefitObjectCode) && enableFringeBenefitCalculationByBenefitRate){ String laborBenefitRateCategoryCode = positionObjectBenefit.getLaborBenefitRateCategoryCode(); // Use parameter default if labor benefit rate category code is blank if(StringUtils.isBlank(laborBenefitRateCategoryCode)){ laborBenefitRateCategoryCode = parameterService.getParameterValueAsString(Account.class, LaborConstants.BenefitCalculation.DEFAULT_BENEFIT_RATE_CATEGORY_CODE_PARAMETER); } //create a map for the search criteria to lookup the fringe benefit percentage Map<String, Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, positionObjectBenefit.getUniversityFiscalYear()); fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, positionObjectBenefit.getChartOfAccountsCode()); fieldValues.put(LaborPropertyConstants.POSITION_BENEFIT_TYPE_CODE, positionObjectBenefit.getFinancialObjectBenefitsTypeCode()); fieldValues.put(LaborPropertyConstants.LABOR_BENEFIT_RATE_CATEGORY_CODE,laborBenefitRateCategoryCode); BenefitsCalculation bc = (BenefitsCalculation) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BenefitsCalculation.class, fieldValues); fringeBenefitObjectCode = bc.getPositionFringeBenefitObjectCode(); } List<LaborLedgerPendingEntry> pendingEntries = generateBenefitPendingEntries(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode); benefitPendingEntries.addAll(pendingEntries); } } return benefitPendingEntries; } /** * generate the benefit pending entries with the given benefit amount and finge benefit object code based on the given document * and accounting line * * @param document the given accounting document * @param accountingLine the given accounting line * @param sequenceHelper the given sequence helper * @param benefitAmount the given benefit amount * @param fringeBenefitObjectCode the given fringe benefit object code * @return a set of benefit pending entries with the given benefit amount and fringe benefit object code */ public static List<LaborLedgerPendingEntry> generateBenefitPendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, KualiDecimal benefitAmount, String fringeBenefitObjectCode) { List<LaborLedgerPendingEntry> benefitPendingEntries = new ArrayList<LaborLedgerPendingEntry>(); LaborLedgerPendingEntry benefitPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitPendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode); benefitPendingEntries.add(benefitPendingEntry); // KFSMI-6863: always create A2 entries regardless of fiscal period LaborLedgerPendingEntry benefitA21PendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitA21PendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode); benefitPendingEntries.add(benefitA21PendingEntry); LaborLedgerPendingEntry benefitA21ReversalPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitA21ReversalPendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode); benefitPendingEntries.add(benefitA21ReversalPendingEntry); //refresh nonupdateable references for financial object... refreshObjectCodeNonUpdateableReferences(benefitPendingEntries); return benefitPendingEntries; } /** * generate the benefit clearing pending entries with the given benefit amount and fringe benefit object code based on the given * document and accounting line * * @param document the given accounting document * @param sequenceHelper the given sequence helper * @param accountNumber the given clearing account number * @param chartOfAccountsCode the given clearing chart of accounts code * @return a set of benefit clearing pending entries */ public static List<LaborLedgerPendingEntry> generateBenefitClearingPendingEntries(LaborLedgerPostingDocument document, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, String accountNumber, String chartOfAccountsCode) { List<LaborLedgerPendingEntry> benefitClearingPendingEntries = new ArrayList<LaborLedgerPendingEntry>(); Map<String, KualiDecimal> sourceLineBenefitAmountSum = new HashMap<String, KualiDecimal>(); List<ExpenseTransferSourceAccountingLine> sourceAccountingLines = document.getSourceAccountingLines(); for (ExpenseTransferSourceAccountingLine accountingLine : sourceAccountingLines) { updateBenefitAmountSum(sourceLineBenefitAmountSum, accountingLine); } Map<String, KualiDecimal> targetLineBenefitAmountSum = new HashMap<String, KualiDecimal>(); List<ExpenseTransferTargetAccountingLine> targetAccountingLines = document.getTargetAccountingLines(); for (ExpenseTransferTargetAccountingLine accountingLine : targetAccountingLines) { updateBenefitAmountSum(targetLineBenefitAmountSum, accountingLine); } Set<String> benefitTypeCodes = new HashSet<String>(); for (String key : targetLineBenefitAmountSum.keySet()) { benefitTypeCodes.add(key); } for (String key : sourceLineBenefitAmountSum.keySet()) { benefitTypeCodes.add(key); } for (String benefitTypeCode : benefitTypeCodes) { KualiDecimal targetAmount = KualiDecimal.ZERO; if (targetLineBenefitAmountSum.containsKey(benefitTypeCode)) { targetAmount = targetLineBenefitAmountSum.get(benefitTypeCode); } KualiDecimal sourceAmount = KualiDecimal.ZERO; if (sourceLineBenefitAmountSum.containsKey(benefitTypeCode)) { sourceAmount = sourceLineBenefitAmountSum.get(benefitTypeCode); } KualiDecimal clearingAmount = sourceAmount.subtract(targetAmount); if (clearingAmount.isNonZero() && ObjectUtils.isNotNull(benefitTypeCode)) { benefitClearingPendingEntries.add(SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitClearingPendingEntry(document, sequenceHelper, accountNumber, chartOfAccountsCode, benefitTypeCode, clearingAmount)); } } //refresh nonupdateable references for financial object... refreshObjectCodeNonUpdateableReferences(benefitClearingPendingEntries); return benefitClearingPendingEntries; } /** * update the benefit amount summary map based on the given accounting line * * @param benefitAmountSumByBenefitType the given benefit amount summary map * @param accountingLine the given accounting line */ protected static void updateBenefitAmountSum(Map<String, KualiDecimal> benefitAmountSumByBenefitType, ExpenseTransferAccountingLine accountingLine) { accountingLine.refreshReferenceObject(KFSPropertyConstants.LABOR_OBJECT); if (ObjectUtils.isNull(accountingLine.getLaborObject())) { return; } String FringeOrSalaryCode = accountingLine.getLaborObject().getFinancialObjectFringeOrSalaryCode(); if (!LaborConstants.SalaryExpenseTransfer.LABOR_LEDGER_SALARY_CODE.equals(FringeOrSalaryCode)) { return; } Integer payrollFiscalyear = accountingLine.getPayrollEndDateFiscalYear(); String chartOfAccountsCode = accountingLine.getChartOfAccountsCode(); String objectCode = accountingLine.getFinancialObjectCode(); Collection<PositionObjectBenefit> positionObjectBenefits = SpringContext.getBean(LaborPositionObjectBenefitService.class).getActivePositionObjectBenefits(payrollFiscalyear, chartOfAccountsCode, objectCode); for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) { positionObjectBenefit.setLaborBenefitRateCategoryCode(accountingLine.getAccount().getLaborBenefitRateCategoryCode()); String benefitTypeCode = positionObjectBenefit.getBenefitsCalculation().getPositionBenefitTypeCode(); KualiDecimal benefitAmount = SpringContext.getBean(LaborBenefitsCalculationService.class).calculateFringeBenefit(positionObjectBenefit, accountingLine.getAmount(), accountingLine.getAccountNumber(), accountingLine.getSubAccountNumber()); if (benefitAmountSumByBenefitType.containsKey(benefitTypeCode)) { benefitAmount = benefitAmount.add(benefitAmountSumByBenefitType.get(benefitTypeCode)); } benefitAmountSumByBenefitType.put(benefitTypeCode, benefitAmount); } } /** * determine if the pay fiscal year and period from the accounting line match with its university fiscal year and period. * * @param document the given document * @param accountingLine the given accounting line of the document * @return true if the pay fiscal year and period from the accounting line match with its university fiscal year and period; * otherwise, false */ protected static boolean isAccountingLinePayFYPeriodMatchesUniversityPayFYPeriod(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine) { Integer fiscalYear = document.getPostingYear(); Integer payFiscalYear = accountingLine.getPayrollEndDateFiscalYear(); if (!fiscalYear.equals(payFiscalYear)) { return false; } String periodCode = document.getPostingPeriodCode(); String payPeriodCode = accountingLine.getPayrollEndDateFiscalPeriodCode(); if (!StringUtils.equals(periodCode, payPeriodCode)) { return false; } return true; } /** * refreshes labor ledger pending entry's object codes. * * @param llpes */ protected static void refreshObjectCodeNonUpdateableReferences(List<LaborLedgerPendingEntry> llpes) { BusinessObjectService bos = SpringContext.getBean(BusinessObjectService.class); //refresh nonupdateable references for financial object... Map<String, String> primaryKeys = new HashMap<String,String>(); for (LaborLedgerPendingEntry llpe : llpes) { primaryKeys.put("financialObjectCode", llpe.getFinancialObjectCode()); ObjectCode objectCode = bos.findByPrimaryKey(ObjectCode.class, primaryKeys); llpe.setFinancialObject(objectCode); } } }