/*
* 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.gl.batch;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.service.ObjectTypeService;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.ObjectHelper;
import org.kuali.kfs.gl.batch.service.impl.exception.FatalErrorException;
import org.kuali.kfs.gl.businessobject.Balance;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* This class helps generate the entries for the nominal activity closing year end job.
*/
public class NominalActivityClosingHelper {
private Integer fiscalYear;
private Date transactionDate;
private String varNetExpenseObjectCode;
private String varNetRevenueObjectCode;
private String varFundBalanceObjectCode;
private String varFundBalanceObjectTypeCode;
private String[] expenseObjectCodeTypes;
private ParameterService parameterService;
private ConfigurationService configurationService;
private FlexibleOffsetAccountService flexibleOffsetService;
private Logger LOG = Logger.getLogger(NominalActivityClosingHelper.class);
private int nonFatalErrorCount;
private List<String> varCharts;
/**
* Constructs a NominalActivityClosingHelper
* @param fiscalYear the fiscal year this job is being run for
* @param transactionDate the transaction date that origin entries should hit the ledger
* @param parameterService an implementation of the ParameterService
* @param configurationService an implementation of the ConfigurationService
*/
public NominalActivityClosingHelper(Integer fiscalYear, Date transactionDate, ParameterService parameterService, ConfigurationService configurationService, ObjectTypeService objectTypeService) {
this.fiscalYear = fiscalYear;
this.transactionDate = transactionDate;
this.parameterService = parameterService;
this.configurationService = configurationService;
this.flexibleOffsetService = SpringContext.getBean(FlexibleOffsetAccountService.class);
this.nonFatalErrorCount = 0;
List<String> objectTypes = objectTypeService.getExpenseObjectTypes(fiscalYear);
expenseObjectCodeTypes = objectTypes.toArray(new String[0]);
varNetExpenseObjectCode = parameterService.getParameterValueAsString(NominalActivityClosingStep.class, "NET_EXPENSE_OBJECT_CODE");
varNetRevenueObjectCode = parameterService.getParameterValueAsString(NominalActivityClosingStep.class, "NET_REVENUE_OBJECT_CODE");
varFundBalanceObjectCode = parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_FUND_BALANCE_OBJECT_CODE_PARM);
varFundBalanceObjectTypeCode = parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_FUND_BALANCE_OBJECT_TYPE_PARM);
//Obtain list of charts to close from Parameter ANNUAL_CLOSING_CHARTS_PARAM.
//If no parameter value exists, act on all charts which is the default action in the delivered foundation code.
varCharts = new ArrayList<String>();
Collection<String> annualClosingCharts = parameterService.getParameterValuesAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_CHARTS_PARAM);
if (ObjectUtils.isNotNull(annualClosingCharts) && (!annualClosingCharts.isEmpty())) {
//transfer charts from parameter to List
varCharts.addAll(annualClosingCharts);
LOG.info("NominalActivityClosingJob ANNUAL_CLOSING_CHARTS parameter value = " + varCharts.toString());
}
}
/**
* Generates an origin entry that will summarize close out of nominal items (income and expense)
* @param balance the balance this activity closing entry needs to be created for
* @param sequenceNumber the sequence number of the origin entry
* @return an origin entry which will close out nominal activity on a balance
* @throws FatalErrorException thrown if the given balance lacks an object type code
*/
public OriginEntryFull generateActivityEntry(Balance balance, Integer sequenceNumber) throws FatalErrorException {
OriginEntryFull activityEntry = new OriginEntryFull();
activityEntry.setUniversityFiscalYear(fiscalYear);
activityEntry.setChartOfAccountsCode(balance.getChartOfAccountsCode());
activityEntry.setAccountNumber(balance.getAccountNumber());
activityEntry.setSubAccountNumber(balance.getSubAccountNumber());
if (ObjectHelper.isOneOf(balance.getObjectTypeCode(), expenseObjectCodeTypes)) {
activityEntry.setFinancialObjectCode(varNetExpenseObjectCode);
}
else {
activityEntry.setFinancialObjectCode(varNetRevenueObjectCode);
}
activityEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
activityEntry.setFinancialBalanceTypeCode(balance.getOption().getNominalFinancialBalanceTypeCd());
if (null == balance.getObjectTypeCode()) {
throw new FatalErrorException(" BALANCE SELECTED FOR PROCESSING IS MISSING ITS OBJECT TYPE CODE ");
}
activityEntry.setFinancialObjectTypeCode(balance.getObjectTypeCode());
activityEntry.setUniversityFiscalPeriodCode(KFSConstants.MONTH13);
activityEntry.setFinancialDocumentTypeCode(parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE));
activityEntry.setFinancialSystemOriginationCode(parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ORIGINATION_CODE));
activityEntry.setDocumentNumber(new StringBuffer(balance.getOption().getActualFinancialBalanceTypeCd()).append(balance.getAccountNumber()).toString());
activityEntry.setTransactionLedgerEntrySequenceNumber(sequenceNumber);
if (ObjectHelper.isOneOf(balance.getObjectTypeCode(), expenseObjectCodeTypes)) {
activityEntry.setTransactionLedgerEntryDescription(this.createTransactionLedgerEntryDescription(configurationService.getPropertyValueAsString(KFSKeyConstants.MSG_CLOSE_ENTRY_TO_NOMINAL_EXPENSE), balance));
}
else {
activityEntry.setTransactionLedgerEntryDescription(this.createTransactionLedgerEntryDescription(configurationService.getPropertyValueAsString(KFSKeyConstants.MSG_CLOSE_ENTRY_TO_NOMINAL_REVENUE), balance));
}
activityEntry.setTransactionLedgerEntryAmount(balance.getAccountLineAnnualBalanceAmount());
activityEntry.setFinancialObjectTypeCode(balance.getObjectTypeCode());
String debitCreditCode = null;
if (null != balance.getObjectType()) {
if (ObjectHelper.isOneOf(balance.getObjectType().getFinObjectTypeDebitcreditCd(), new String[] { KFSConstants.GL_CREDIT_CODE, KFSConstants.GL_DEBIT_CODE })) {
debitCreditCode = balance.getObjectType().getFinObjectTypeDebitcreditCd();
}
else {
debitCreditCode = KFSConstants.GL_CREDIT_CODE;
}
// NOTE (laran) The amount on the origin entry is set to this value above.
// NOTE (laran) I'm using the balance value here because to me it was easier than remembering the
// indirection.
if (balance.getAccountLineAnnualBalanceAmount().isNegative()) {
if (ObjectHelper.isOneOf(balance.getObjectType().getFinObjectTypeDebitcreditCd(), new String[] { KFSConstants.GL_CREDIT_CODE, KFSConstants.GL_DEBIT_CODE })) {
activityEntry.setTransactionDebitCreditCode(balance.getObjectType().getFinObjectTypeDebitcreditCd());
}
else {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
}
else {
if (KFSConstants.GL_CREDIT_CODE.equals(balance.getObjectType().getFinObjectTypeDebitcreditCd())) {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
else {
if (KFSConstants.GL_DEBIT_CODE.equals(balance.getObjectType().getFinObjectTypeDebitcreditCd())) {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
}
}
}
else {
if (balance.getAccountLineAnnualBalanceAmount().isNegative()) {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else {
activityEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
nonFatalErrorCount += 1;
LOG.info(new StringBuffer("FIN OBJ TYP CODE ").append(balance.getObjectTypeCode()).append(" NOT IN TABLE").toString());
}
activityEntry.setTransactionDate(transactionDate);
activityEntry.setOrganizationDocumentNumber(null);
activityEntry.setProjectCode(KFSConstants.getDashProjectCode());
activityEntry.setOrganizationReferenceId(null);
activityEntry.setReferenceFinancialDocumentTypeCode(null);
activityEntry.setReferenceFinancialSystemOriginationCode(null);
activityEntry.setReferenceFinancialDocumentNumber(null);
activityEntry.setReversalDate(null);
activityEntry.setTransactionEncumbranceUpdateCode(null);
if (balance.getAccountLineAnnualBalanceAmount().isNegative()) {
activityEntry.setTransactionLedgerEntryAmount(balance.getAccountLineAnnualBalanceAmount().negated());
}
return activityEntry;
}
/**
* Genereates an origin entry to update a fund balance as a result of closing income and expense
* @param balance the balance this offset needs to be created for
* @param sequenceNumber the sequence number of the origin entry full
* @return an origin entry which will offset the nominal closing activity
* @throws FatalErrorException thrown if the given balance lacks an object type code
*/
public OriginEntryFull generateOffset(Balance balance, Integer sequenceNumber) throws FatalErrorException {
String debitCreditCode = null;
if (null == balance.getObjectTypeCode()) {
throw new FatalErrorException(" BALANCE SELECTED FOR PROCESSING IS MISSING ITS OBJECT TYPE CODE ");
}
if (ObjectHelper.isOneOf(balance.getObjectType().getFinObjectTypeDebitcreditCd(), new String[] { KFSConstants.GL_CREDIT_CODE, KFSConstants.GL_DEBIT_CODE })) {
debitCreditCode = balance.getObjectType().getFinObjectTypeDebitcreditCd();
}
else {
debitCreditCode = KFSConstants.GL_CREDIT_CODE;
}
OriginEntryFull offsetEntry = new OriginEntryFull();
offsetEntry.setUniversityFiscalYear(fiscalYear);
offsetEntry.setChartOfAccountsCode(balance.getChartOfAccountsCode());
offsetEntry.setAccountNumber(balance.getAccountNumber());
offsetEntry.setSubAccountNumber(balance.getSubAccountNumber());
offsetEntry.setFinancialObjectCode(varFundBalanceObjectCode);
offsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
offsetEntry.setFinancialBalanceTypeCode(balance.getOption().getNominalFinancialBalanceTypeCd());
offsetEntry.setFinancialObjectTypeCode(varFundBalanceObjectTypeCode);
offsetEntry.setUniversityFiscalPeriodCode(KFSConstants.MONTH13);
offsetEntry.setFinancialDocumentTypeCode(parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE));
offsetEntry.setFinancialSystemOriginationCode(parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ORIGINATION_CODE));
offsetEntry.setDocumentNumber(new StringBuffer(balance.getOption().getActualFinancialBalanceTypeCd()).append(balance.getAccountNumber()).toString());
offsetEntry.setTransactionLedgerEntrySequenceNumber(new Integer(sequenceNumber.intValue()));
offsetEntry.setTransactionLedgerEntryDescription(this.createTransactionLedgerEntryDescription(configurationService.getPropertyValueAsString(KFSKeyConstants.MSG_CLOSE_ENTRY_TO_FUND_BALANCE), balance));
offsetEntry.setTransactionLedgerEntryAmount(balance.getAccountLineAnnualBalanceAmount());
offsetEntry.setTransactionDebitCreditCode(debitCreditCode);
offsetEntry.setTransactionDate(transactionDate);
offsetEntry.setOrganizationDocumentNumber(null);
offsetEntry.setProjectCode(KFSConstants.getDashProjectCode());
offsetEntry.setOrganizationReferenceId(null);
offsetEntry.setReferenceFinancialDocumentTypeCode(null);
offsetEntry.setReferenceFinancialSystemOriginationCode(null);
offsetEntry.setReferenceFinancialDocumentNumber(null);
offsetEntry.setFinancialDocumentReversalDate(null);
offsetEntry.setTransactionEncumbranceUpdateCode(null);
if (balance.getAccountLineAnnualBalanceAmount().isNegative()) {
if (KFSConstants.GL_CREDIT_CODE.equals(debitCreditCode)) {
offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
else {
offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
}
if (balance.getAccountLineAnnualBalanceAmount().isNegative()) {
offsetEntry.setTransactionLedgerEntryAmount(balance.getAccountLineAnnualBalanceAmount().negated());
}
flexibleOffsetService.updateOffset(offsetEntry);
return offsetEntry;
}
/**
* Adds the job parameters used to generate the origin entries to the given map
* @param nominalClosingJobParameters a map of batch job parameters to add nominal activity closing parameters to
*/
public void addNominalClosingJobParameters(Map nominalClosingJobParameters) {
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.UNIVERSITY_FISCAL_YEAR, fiscalYear);
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.NET_EXP_OBJECT_CD, varNetExpenseObjectCode);
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.NET_REV_OBJECT_CD, varNetRevenueObjectCode);
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.FUND_BAL_OBJECT_CD, varFundBalanceObjectCode);
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.FUND_BAL_OBJ_TYP_CD, varFundBalanceObjectTypeCode);
nominalClosingJobParameters.put(GeneralLedgerConstants.ColumnNames.CHART_OF_ACCOUNTS_CODE, varCharts);
}
/**
* Generates the transaction ledger entry description for a given balance
*
* @param descriptorIntro the introduction to the description
* @param balance the balance the transaction description will refer to
* @return the generated transaction ledger entry description
*/
private String createTransactionLedgerEntryDescription(String descriptorIntro, Balance balance) {
StringBuilder description = new StringBuilder();
description.append(descriptorIntro.trim()).append(' ');
return description.append(getSizedField(5, balance.getSubAccountNumber())).append("-").append(getSizedField(4, balance.getObjectCode())).append("-").append(getSizedField(3, balance.getSubObjectCode())).append("-").append(getSizedField(2, balance.getObjectTypeCode())).toString();
}
/**
* Pads out a string so that it will be a certain length
*
* @param size the size to pad to
* @param value the String being padded
* @return the padded String
*/
private StringBuilder getSizedField(int size, String value) {
StringBuilder fieldString = new StringBuilder();
if (value != null) {
fieldString.append(value);
while (fieldString.length() < size) {
fieldString.append(' ');
}
}
else {
while (fieldString.length() < size) {
fieldString.append('-');
}
}
return fieldString;
}
/**
* Returns the count of non-fatal errors encountered during the process by this helper
* @return the count of non-fatal errors
*/
public Integer getNonFatalErrorCount() {
return new Integer(this.nonFatalErrorCount);
}
/**
* Returns the boolean from the chart parameter list being empty
* @return isEmpty boolean value for chart List
*/
public boolean isAnnualClosingChartParamterBlank(){
return varCharts.isEmpty();
}
}