/*
* 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.fp.document.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.fp.businessobject.BudgetAdjustmentAccountingLine;
import org.kuali.kfs.fp.document.BudgetAdjustmentDocument;
import org.kuali.kfs.fp.document.service.BudgetAdjustmentLaborBenefitsService;
import org.kuali.kfs.integration.ld.LaborLedgerBenefitsCalculation;
import org.kuali.kfs.integration.ld.LaborLedgerPositionObjectBenefit;
import org.kuali.kfs.integration.ld.LaborModuleService;
import org.kuali.kfs.sys.KFSParameterKeyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
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.core.api.util.type.KualiInteger;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* This is the default implementation of the methods defined by the BudgetAdjustmentLaborBenefitsService. These service performs
* methods related to the generation of labor benefit accounting lines for the budget adjustment document.
*/
public class BudgetAdjustmentLaborBenefitsServiceImpl implements BudgetAdjustmentLaborBenefitsService {
private BusinessObjectService businessObjectService;
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BudgetAdjustmentLaborBenefitsServiceImpl.class);
/**
* This method generated labor benefit accounting lines to be added to the BudgetDocument provided.
*
* @param budgetDocument The BudgetDocument to have the new labor benefit accounting lines added to.
* @see org.kuali.kfs.fp.document.service.BudgetAdjustmentLaborBenefitsService#generateLaborBenefitsAccountingLines(org.kuali.kfs.fp.document.BudgetAdjustmentDocument)
*/
@Override
public void generateLaborBenefitsAccountingLines(BudgetAdjustmentDocument budgetDocument) {
Integer fiscalYear = budgetDocument.getPostingYear();
List accountingLines = new ArrayList();
accountingLines.addAll(budgetDocument.getSourceAccountingLines());
accountingLines.addAll(budgetDocument.getTargetAccountingLines());
/*
* find lines that have labor object codes, then retrieve the benefit calculation records for the object code. Finally, for
* each benefit record, create an accounting line with properties set from the original line, but substituted with the
* benefit object code and calculated current and base amount.
*/
for (Iterator iter = accountingLines.iterator(); iter.hasNext();) {
BudgetAdjustmentAccountingLine line = (BudgetAdjustmentAccountingLine) iter.next();
// check if the line was previousely generated benefit line, if so delete and skip
if (line.isFringeBenefitIndicator()) {
if (line.isSourceAccountingLine()) {
budgetDocument.getSourceAccountingLines().remove(line);
}
else {
budgetDocument.getTargetAccountingLines().remove(line);
}
continue;
}
List<BudgetAdjustmentAccountingLine> benefitLines = generateBenefitLines(fiscalYear, line, budgetDocument);
for (BudgetAdjustmentAccountingLine benefitLine : benefitLines) {
if (benefitLine.isSourceAccountingLine()) {
budgetDocument.addSourceAccountingLine((SourceAccountingLine) benefitLine);
}
else {
budgetDocument.addTargetAccountingLine((TargetAccountingLine) benefitLine);
}
}
}
}
/**
* Given a budget adjustment accounting line, generates appropriate fringe benefit lines for the line
*
* @param line a line to generate fringe benefit lines for
* @return a List of BudgetAdjustmentAccountingLines to add to the document as fringe benefit lines
*/
protected List<BudgetAdjustmentAccountingLine> generateBenefitLines(Integer fiscalYear, BudgetAdjustmentAccountingLine line, BudgetAdjustmentDocument document) {
List<BudgetAdjustmentAccountingLine> fringeLines = new ArrayList<BudgetAdjustmentAccountingLine>();
try {
Collection<LaborLedgerPositionObjectBenefit> objectBenefits = SpringContext.getBean(LaborModuleService.class).retrieveActiveLaborPositionObjectBenefits(fiscalYear, line.getChartOfAccountsCode(), line.getFinancialObjectCode());
if (objectBenefits != null) {
for (LaborLedgerPositionObjectBenefit fringeBenefitInformation : objectBenefits) {
// now create and set properties for the benefit line
BudgetAdjustmentAccountingLine benefitLine = null;
if (line.isSourceAccountingLine()) {
benefitLine = (BudgetAdjustmentAccountingLine) document.getSourceAccountingLineClass().newInstance();
}
else {
benefitLine = (BudgetAdjustmentAccountingLine) document.getTargetAccountingLineClass().newInstance();
}
// create a map to use in the lookup of the account
Map<String, Object> fieldValues = new HashMap<String, Object>();
fieldValues.put("chartOfAccountsCode", line.getChartOfAccountsCode());
fieldValues.put("accountNumber", line.getAccountNumber());
// use the budget adjustment accounting line to get the account number that will then be used to lookup the
// labor benefit rate category code
Account lookupAccount = businessObjectService.findByPrimaryKey(Account.class, fieldValues);
LaborLedgerBenefitsCalculation benefitsCalculation = null;
String laborBenefitsRateCategoryCode = "";
// make sure the parameter exists
if (SpringContext.getBean(ParameterService.class).parameterExists(Account.class, KFSParameterKeyConstants.LdParameterConstants.DEFAULT_BENEFIT_RATE_CATEGORY_CODE)) {
laborBenefitsRateCategoryCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(Account.class, KFSParameterKeyConstants.LdParameterConstants.DEFAULT_BENEFIT_RATE_CATEGORY_CODE);
}
else {
laborBenefitsRateCategoryCode = "";
}
// make sure the system parameter exists
if (SpringContext.getBean(ParameterService.class).parameterExists(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND)) {
// check the system param to see if the labor benefit rate category should be filled in
String sysParam = SpringContext.getBean(ParameterService.class).getParameterValueAsString(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND);
LOG.debug("sysParam: " + sysParam);
// if sysParam == Y then Labor Benefit Rate Category should be used in the search
if (sysParam.equalsIgnoreCase("Y")) {
if (StringUtils.isBlank(line.getSubAccount().getSubAccountNumber())) {
laborBenefitsRateCategoryCode = lookupAccount.getLaborBenefitRateCategoryCode();
}
else {
laborBenefitsRateCategoryCode = SpringContext.getBean(LaborModuleService.class).getBenefitRateCategoryCode(line.getChartOfAccountsCode(), line.getAccountNumber(), line.getSubAccount().getSubAccountNumber());
}
// make sure laborBenefitsRateCategoryCode isn't null
if (ObjectUtils.isNull(laborBenefitsRateCategoryCode)) {
// make sure the parameter exists
if (SpringContext.getBean(ParameterService.class).parameterExists(Account.class, KFSParameterKeyConstants.LdParameterConstants.DEFAULT_BENEFIT_RATE_CATEGORY_CODE)) {
laborBenefitsRateCategoryCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(Account.class, KFSParameterKeyConstants.LdParameterConstants.DEFAULT_BENEFIT_RATE_CATEGORY_CODE);
}
else {
laborBenefitsRateCategoryCode = "";
}
}
}
}
String beneCalc = "{" + fringeBenefitInformation.getUniversityFiscalYear() + "," + fringeBenefitInformation.getChartOfAccountsCode() + "," + fringeBenefitInformation.getFinancialObjectBenefitsTypeCode() + "," + laborBenefitsRateCategoryCode + "}";
LOG.info("Looking for a benefits calculation for " + beneCalc);
// get the benefits calculation taking the laborBenefitRateCategoryCode into account
benefitsCalculation = fringeBenefitInformation.getLaborLedgerBenefitsCalculation(laborBenefitsRateCategoryCode);
if (benefitsCalculation != null) {
LOG.info("Found benefits calculation for " + beneCalc);
}
else {
LOG.info("Couldn't locate a benefits calculation for " + beneCalc);
}
if ((benefitsCalculation != null) && benefitsCalculation.isActive()) {
benefitLine.copyFrom(line);
benefitLine.setFinancialObjectCode(benefitsCalculation.getPositionFringeBenefitObjectCode());
benefitLine.refreshNonUpdateableReferences();
LaborModuleService laborModuleService = SpringContext.getBean(LaborModuleService.class);
if (ObjectUtils.isNotNull(laborModuleService.getCostSharingSourceAccountNumber())) {
benefitLine.setAccountNumber(laborModuleService.getCostSharingSourceAccountNumber());
benefitLine.setSubAccountNumber(laborModuleService.getCostSharingSourceSubAccountNumber());
benefitLine.setChartOfAccountsCode(laborModuleService.getCostSharingSourceChartOfAccountsCode());
}
benefitLine.refreshNonUpdateableReferences();
// convert whole percentage to decimal value (5% to .0500, 18.66% to 0.1866)
BigDecimal fringeBenefitPercent = formatPercentageForMultiplication(benefitsCalculation.getPositionFringeBenefitPercent());
// compute the benefit current amount with all decimals and then round it to the closest integer by setting the
// scale to 0 and using the round half up rounding mode: exp. 1200*0.1866 = 223.92 -> rounded to 224
BigDecimal benefitCurrentAmount = line.getCurrentBudgetAdjustmentAmount().bigDecimalValue().multiply(fringeBenefitPercent);
benefitCurrentAmount = benefitCurrentAmount.setScale(2, BigDecimal.ROUND_HALF_UP);
benefitLine.setCurrentBudgetAdjustmentAmount(new KualiDecimal(benefitCurrentAmount));
KualiInteger benefitBaseAmount = line.getBaseBudgetAdjustmentAmount().multiply(fringeBenefitPercent);
benefitLine.setBaseBudgetAdjustmentAmount(benefitBaseAmount);
// clear monthly lines per KULEDOCS-1606
benefitLine.clearFinancialDocumentMonthLineAmounts();
// set flag on line so we know it was a generated benefit line and can clear it out later if needed
benefitLine.setFringeBenefitIndicator(true);
fringeLines.add(benefitLine);
}
}
}
}
catch (InstantiationException ie) {
// it's doubtful this catch block or the catch block below are ever accessible, as accounting lines should already have
// been generated
// for the document. But we can still make it somebody else's problem
throw new RuntimeException(ie);
}
catch (IllegalAccessException iae) {
// with some luck we'll pass the buck now sez some other dev "This sucks!" Get your Runtime on!
// but really...we'll never make it this far. I promise.
throw new RuntimeException(iae);
}
return fringeLines;
}
/**
* @param budgetDocument
* @return
* @see org.kuali.kfs.fp.document.service.BudgetAdjustmentLaborBenefitsService#hasLaborObjectCodes(org.kuali.kfs.fp.document.BudgetAdjustmentDocument)
*/
@Override
public boolean hasLaborObjectCodes(BudgetAdjustmentDocument budgetDocument) {
boolean hasLaborObjectCodes = false;
List<AccountingLine> accountingLines = new ArrayList<AccountingLine>();
accountingLines.addAll(budgetDocument.getSourceAccountingLines());
accountingLines.addAll(budgetDocument.getTargetAccountingLines());
Integer fiscalYear = budgetDocument.getPostingYear();
LaborModuleService laborModuleService = SpringContext.getBean(LaborModuleService.class);
for (AccountingLine line : accountingLines) {
if (laborModuleService.hasFringeBenefitProducingObjectCodes(fiscalYear, line.getChartOfAccountsCode(), line.getFinancialObjectCode())) {
hasLaborObjectCodes = true;
break;
}
}
return hasLaborObjectCodes;
}
/**
* Formats the stored percentage to be used in multiplication. For example if the percentage is 18.66 it will return 0.1866. The
* returned number will always have 4 digits.
*
* @param percent the stored percent
* @return percentage formatted for multiplication
*/
protected BigDecimal formatPercentageForMultiplication(KualiDecimal percent) {
BigDecimal result = BigDecimal.ZERO;
if (percent != null) {
result = percent.bigDecimalValue().divide(new BigDecimal(100), 4, BigDecimal.ROUND_HALF_UP);
}
return result;
}
/**
* Gets the businessObjectService attribute.
*
* @return Returns the businessObjectService.
*/
public BusinessObjectService getBusinessObjectService() {
return businessObjectService;
}
/**
* Sets the businessObjectService attribute value.
*
* @param businessObjectService The businessObjectService to set.
*/
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
}