/*
* 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.service.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.sql.Date;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
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.A21IndirectCostRecoveryAccount;
import org.kuali.kfs.coa.businessobject.A21SubAccount;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountingPeriod;
import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryAccount;
import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRate;
import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRateDetail;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.businessobject.OffsetDefinition;
import org.kuali.kfs.coa.businessobject.SubAccount;
import org.kuali.kfs.coa.dataaccess.IndirectCostRecoveryRateDetailDao;
import org.kuali.kfs.coa.service.AccountingPeriodService;
import org.kuali.kfs.coa.service.ObjectCodeService;
import org.kuali.kfs.coa.service.OffsetDefinitionService;
import org.kuali.kfs.coa.service.SubAccountService;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.PosterIndirectCostRecoveryEntriesStep;
import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
import org.kuali.kfs.gl.batch.service.PostTransaction;
import org.kuali.kfs.gl.batch.service.PosterService;
import org.kuali.kfs.gl.batch.service.RunDateService;
import org.kuali.kfs.gl.batch.service.VerifyTransaction;
import org.kuali.kfs.gl.businessobject.ExpenditureTransaction;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.gl.businessobject.Reversal;
import org.kuali.kfs.gl.businessobject.Transaction;
import org.kuali.kfs.gl.dataaccess.ExpenditureTransactionDao;
import org.kuali.kfs.gl.dataaccess.ReversalDao;
import org.kuali.kfs.gl.report.LedgerSummaryReport;
import org.kuali.kfs.gl.report.TransactionListingReport;
import org.kuali.kfs.gl.service.OriginEntryGroupService;
import org.kuali.kfs.gl.service.OriginEntryService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.Message;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.businessobject.UniversityDate;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.dataaccess.UniversityDateDao;
import org.kuali.kfs.sys.exception.InvalidFlexibleOffsetException;
import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
import org.kuali.kfs.sys.service.ReportWriterService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.PersistenceService;
import org.kuali.rice.krad.service.PersistenceStructureService;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
/**
* The base implementation of PosterService
*/
@Transactional
public class PosterServiceImpl implements PosterService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PosterServiceImpl.class);
public static final KualiDecimal WARNING_MAX_DIFFERENCE = new KualiDecimal("0.03");
public static final String DATE_FORMAT_STRING = "yyyyMMdd";
private List transactionPosters;
private VerifyTransaction verifyTransaction;
private OriginEntryService originEntryService;
private OriginEntryGroupService originEntryGroupService;
private DateTimeService dateTimeService;
private ReversalDao reversalDao;
private UniversityDateDao universityDateDao;
private AccountingPeriodService accountingPeriodService;
private ExpenditureTransactionDao expenditureTransactionDao;
private IndirectCostRecoveryRateDetailDao indirectCostRecoveryRateDetailDao;
private ObjectCodeService objectCodeService;
private ParameterService parameterService;
private ConfigurationService configurationService;
private FlexibleOffsetAccountService flexibleOffsetAccountService;
private RunDateService runDateService;
private SubAccountService subAccountService;
private OffsetDefinitionService offsetDefinitionService;
private DataDictionaryService dataDictionaryService;
private BusinessObjectService businessObjectService;
private PersistenceStructureService persistenceStructureService;
private ReportWriterService reportWriterService;
private ReportWriterService errorListingReportWriterService;
private ReportWriterService reversalReportWriterService;
private ReportWriterService ledgerSummaryReportWriterService;
//private File OUTPUT_ERR_FILE;
//private PrintStream OUTPUT_ERR_FILE_ps;
//private PrintStream OUTPUT_GLE_FILE_ps;
private String batchFileDirectoryName;
//private BufferedReader INPUT_GLE_FILE_br = null;
//private FileReader INPUT_GLE_FILE = null;
private AccountingCycleCachingService accountingCycleCachingService;
/**
* Post scrubbed GL entries to GL tables.
*/
@Override
public void postMainEntries() {
LOG.debug("postMainEntries() started");
Date runDate = dateTimeService.getCurrentSqlDate();
try{
FileReader INPUT_GLE_FILE = new FileReader(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.POSTER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
postEntries(PosterService.MODE_ENTRIES, INPUT_GLE_FILE, null, OUTPUT_ERR_FILE);
INPUT_GLE_FILE.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
throw new RuntimeException("PosterMainEntries Stopped: " + e1.getMessage(), e1);
} catch (IOException ioe) {
LOG.error("postMainEntries stopped due to: " + ioe.getMessage(), ioe);
throw new RuntimeException(ioe);
}
}
/**
* Post reversal GL entries to GL tables.
*/
@Override
public void postReversalEntries() {
LOG.debug("postReversalEntries() started");
Date runDate = dateTimeService.getCurrentSqlDate();
try{
PrintStream OUTPUT_GLE_FILE_ps = new PrintStream(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.REVERSAL_POSTER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.REVERSAL_POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
postEntries(PosterService.MODE_REVERSAL, null, OUTPUT_GLE_FILE_ps, OUTPUT_ERR_FILE);
OUTPUT_GLE_FILE_ps.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
throw new RuntimeException("PosterReversalEntries Stopped: " + e1.getMessage(), e1);
}
}
/**
* Post ICR GL entries to GL tables.
*/
@Override
public void postIcrEntries() {
LOG.debug("postIcrEntries() started");
Date runDate = dateTimeService.getCurrentSqlDate();
try{
FileReader INPUT_GLE_FILE = new FileReader(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_POSTER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
postEntries(PosterService.MODE_ICR, INPUT_GLE_FILE, null, OUTPUT_ERR_FILE);
INPUT_GLE_FILE.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
throw new RuntimeException("PosterIcrEntries Stopped: " + e1.getMessage(), e1);
} catch (IOException ioe) {
LOG.error("postIcrEntries stopped due to: " + ioe.getMessage(), ioe);
throw new RuntimeException(ioe);
}
}
/**
* Post ICR Encumbrance GL entries to GL tables.
*/
@Override
public void postIcrEncumbranceEntries() {
if (LOG.isDebugEnabled()) {
LOG.debug("postIcrEncumbranceEntries() started");
}
Date runDate = dateTimeService.getCurrentSqlDate();
try{
FileReader INPUT_GLE_FILE = new FileReader(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_ENCUMBRANCE_POSTER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_ENCUMBRANCE_POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
postEntries(PosterService.MODE_ICRENCMB, INPUT_GLE_FILE, null, OUTPUT_ERR_FILE);
INPUT_GLE_FILE.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
throw new RuntimeException("postIcrEncumbranceEntries Stopped: " + e1.getMessage(), e1);
} catch (IOException ioe) {
LOG.error("postIcrEncumbranceEntries stopped due to: " + ioe.getMessage(), ioe);
throw new RuntimeException(ioe);
}
}
/**
* Actually post the entries. The mode variable decides which entries to post.
*
* @param mode the poster's current run mode
*/
protected void postEntries(int mode, FileReader INPUT_GLE_FILE, PrintStream OUTPUT_GLE_FILE_ps, File OUTPUT_ERR_FILE) throws FileNotFoundException {
if (LOG.isDebugEnabled()) {
LOG.debug("postEntries() started");
}
PrintStream OUTPUT_ERR_FILE_ps = new PrintStream(OUTPUT_ERR_FILE);
BufferedReader INPUT_GLE_FILE_br = null;
if (INPUT_GLE_FILE != null) {
INPUT_GLE_FILE_br = new BufferedReader(INPUT_GLE_FILE);
}
String GLEN_RECORD;
Date executionDate = new Date(dateTimeService.getCurrentDate().getTime());
Date runDate = new Date(runDateService.calculateRunDate(executionDate).getTime());
UniversityDate runUniversityDate = SpringContext.getBean(BusinessObjectService.class).findBySinglePrimaryKey(UniversityDate.class, runDate);
LedgerSummaryReport ledgerSummaryReport = new LedgerSummaryReport();
// Build the summary map so all the possible combinations of destination & operation
// are included in the summary part of the report.
Map reportSummary = new HashMap();
for (Iterator posterIter = transactionPosters.iterator(); posterIter.hasNext();) {
PostTransaction poster = (PostTransaction) posterIter.next();
reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.DELETE_CODE, new Integer(0));
reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.INSERT_CODE, new Integer(0));
reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.UPDATE_CODE, new Integer(0));
}
int ecount = 0;
OriginEntryFull tran = null;
Transaction reversalTransaction = null;
try {
if ((mode == PosterService.MODE_ENTRIES) || (mode == PosterService.MODE_ICR) || (mode == PosterService.MODE_ICRENCMB)) {
LOG.debug("postEntries() Processing groups");
while ((GLEN_RECORD = INPUT_GLE_FILE_br.readLine()) != null) {
if (!org.apache.commons.lang.StringUtils.isEmpty(GLEN_RECORD) && !org.apache.commons.lang.StringUtils.isBlank(GLEN_RECORD.trim())) {
ecount++;
GLEN_RECORD = org.apache.commons.lang.StringUtils.rightPad(GLEN_RECORD, 183, ' ');
tran = new OriginEntryFull();
// checking parsing process and stop poster when it has errors.
List<Message> parsingError = new ArrayList();
parsingError = tran.setFromTextFileForBatch(GLEN_RECORD, ecount);
if (parsingError.size() > 0) {
String messages = "";
for(Message msg : parsingError) {messages += msg + " ";}
throw new RuntimeException("Exception happened from parsing process: " + messages);
}
// need to pass ecount for building better message
addReporting(reportSummary, "SEQUENTIAL", GeneralLedgerConstants.SELECT_CODE);
postTransaction(tran, mode, reportSummary, ledgerSummaryReport, OUTPUT_ERR_FILE_ps, runUniversityDate, GLEN_RECORD, OUTPUT_GLE_FILE_ps);
if (ecount % 1000 == 0) {
LOG.info("postEntries() Posted Entry " + ecount);
}
// need to create offset as this was not done in the ICR Encumbrance Feed Step
if (mode == PosterService.MODE_ICRENCMB) {
ecount++;
generateOffset(tran, mode, reportSummary, ledgerSummaryReport, OUTPUT_ERR_FILE_ps, runUniversityDate, GLEN_RECORD, OUTPUT_GLE_FILE_ps);
if (ecount % 1000 == 0) {
LOG.info("postEntries() Posted Entry " + ecount);
}
}
}
}
if (INPUT_GLE_FILE_br != null) {
INPUT_GLE_FILE_br.close();
}
OUTPUT_ERR_FILE_ps.close();
reportWriterService.writeStatisticLine("SEQUENTIAL RECORDS READ %,9d", reportSummary.get("SEQUENTIAL,S"));
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("postEntries() Processing reversal transactions");
}
final String GL_REVERSAL_T = getPersistenceStructureService().getTableName(Reversal.class);
Iterator reversalTransactions = reversalDao.getByDate(runDate);
TransactionListingReport reversalListingReport = new TransactionListingReport();
while (reversalTransactions.hasNext()) {
ecount++;
reversalTransaction = (Transaction) reversalTransactions.next();
addReporting(reportSummary, GL_REVERSAL_T, GeneralLedgerConstants.SELECT_CODE);
boolean posted = postTransaction(reversalTransaction, mode, reportSummary, ledgerSummaryReport, OUTPUT_ERR_FILE_ps, runUniversityDate, GL_REVERSAL_T, OUTPUT_GLE_FILE_ps);
if (posted) {
reversalListingReport.generateReport(reversalReportWriterService, reversalTransaction);
}
if (ecount % 1000 == 0) {
LOG.info("postEntries() Posted Entry " + ecount);
}
}
OUTPUT_ERR_FILE_ps.close();
reportWriterService.writeStatisticLine("GLRV RECORDS READ (GL_REVERSAL_T) %,9d", reportSummary.get("GL_REVERSAL_T,S"));
reversalListingReport.generateStatistics(reversalReportWriterService);
}
//PDF version had this abstracted to print I/U/D for each table in 7 posters, but some statistics are meaningless (i.e. GLEN is never updated), so un-abstracted here
reportWriterService.writeStatisticLine("GLEN RECORDS INSERTED (GL_ENTRY_T) %,9d", reportSummary.get("GL_ENTRY_T,I"));
reportWriterService.writeStatisticLine("GLBL RECORDS INSERTED (GL_BALANCE_T) %,9d", reportSummary.get("GL_BALANCE_T,I"));
reportWriterService.writeStatisticLine("GLBL RECORDS UPDATED (GL_BALANCE_T) %,9d", reportSummary.get("GL_BALANCE_T,U"));
reportWriterService.writeStatisticLine("GLEX RECORDS INSERTED (GL_EXPEND_TRN_MT) %,9d", reportSummary.get("GL_EXPEND_TRN_MT,I"));
reportWriterService.writeStatisticLine("GLEX RECORDS UPDATED (GL_EXPEND_TRN_MT) %,9d", reportSummary.get("GL_EXPEND_TRN_MT,U"));
reportWriterService.writeStatisticLine("GLEC RECORDS INSERTED (GL_ENCUMBRANCE_T) %,9d", reportSummary.get("GL_ENCUMBRANCE_T,I"));
reportWriterService.writeStatisticLine("GLEC RECORDS UPDATED (GL_ENCUMBRANCE_T) %,9d", reportSummary.get("GL_ENCUMBRANCE_T,U"));
reportWriterService.writeStatisticLine("GLRV RECORDS INSERTED (GL_REVERSAL_T) %,9d", reportSummary.get("GL_REVERSAL_T,I"));
reportWriterService.writeStatisticLine("GLRV RECORDS DELETED (GL_REVERSAL_T) %,9d", reportSummary.get("GL_REVERSAL_T,D"));
reportWriterService.writeStatisticLine("SFBL RECORDS INSERTED (GL_SF_BALANCES_T) %,9d", reportSummary.get("GL_SF_BALANCES_T,I"));
reportWriterService.writeStatisticLine("SFBL RECORDS UPDATED (GL_SF_BALANCES_T) %,9d", reportSummary.get("GL_SF_BALANCES_T,U"));
reportWriterService.writeStatisticLine("ACBL RECORDS INSERTED (GL_ACCT_BALANCES_T) %,9d", reportSummary.get("GL_ACCT_BALANCES_T,I"));
reportWriterService.writeStatisticLine("ACBL RECORDS UPDATED (GL_ACCT_BALANCES_T) %,9d", reportSummary.get("GL_ACCT_BALANCES_T,U"));
reportWriterService.writeStatisticLine("ERROR RECORDS WRITTEN %,9d", reportSummary.get("WARNING,I"));
}
catch (RuntimeException re) {
LOG.error("postEntries stopped due to: " + re.getMessage() + ", on line number : " + ecount, re);
// the null checking in the following code doesn't work as intended, since Java evaluates "+" before "==";
// as a result, if reversalTransaction is indeed null, it will cause NPE; and that happened.
// fixing it by adding () around the ? expression
//LOG.error("tran failure occured on: " + tran == null ? null : tran.toString());
//LOG.error("reversalTransaction failure occured on: " + reversalTransaction == null ? null : reversalTransaction.toString());
LOG.error("transaction failure occured on: " + (ObjectUtils.isNull(tran) ? null : tran.toString()));
LOG.error("reversalTransaction failure occured on: " + (ObjectUtils.isNull(reversalTransaction) ? null : reversalTransaction.toString()));
throw new RuntimeException("PosterService Stopped: " + re.getMessage(), re);
}
catch (IOException e) {
LOG.error("postEntries stopped due to: " + e.getMessage(), e);
throw new RuntimeException(e);
}
catch (Exception e) {
// do nothing - handled in postTransaction method.
}
LOG.info("postEntries() done, total count = " + ecount);
// Generate the reports
ledgerSummaryReport.writeReport(ledgerSummaryReportWriterService);
new TransactionListingReport().generateReport(errorListingReportWriterService, new OriginEntryFileIterator(OUTPUT_ERR_FILE));
}
/**
* Runs the given transaction through each transaction posting algorithms associated with this instance
*
* @param tran a transaction to post
* @param mode the mode the poster is running in
* @param reportSummary a Map of summary counts generated by the posting process
* @param ledgerSummaryReport for summary reporting
* @param invalidGroup the group to save invalid entries to
* @param runUniversityDate the university date of this poster run
* @param line
* @return whether the transaction was posted or not. Useful if calling class attempts to report on the transaction
*/
protected boolean postTransaction(Transaction tran, int mode, Map<String,Integer> reportSummary, LedgerSummaryReport ledgerSummaryReport, PrintStream invalidGroup, UniversityDate runUniversityDate, String line, PrintStream OUTPUT_GLE_FILE_ps) {
List<Message> errors = new ArrayList();
Transaction originalTransaction = tran;
try {
final String GL_ORIGIN_ENTRY_T = getPersistenceStructureService().getTableName(OriginEntryFull.class);
// Update select count in the report
if ((mode == PosterService.MODE_ENTRIES) || (mode == PosterService.MODE_ICR) || (mode == PosterService.MODE_ICRENCMB)) {
addReporting(reportSummary, GL_ORIGIN_ENTRY_T, GeneralLedgerConstants.SELECT_CODE);
}
// If these are reversal entries, we need to reverse the entry and
// modify a few fields
if (mode == PosterService.MODE_REVERSAL) {
Reversal reversal = new Reversal(tran);
// Reverse the debit/credit code
if (KFSConstants.GL_DEBIT_CODE.equals(reversal.getTransactionDebitCreditCode())) {
reversal.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else if (KFSConstants.GL_CREDIT_CODE.equals(reversal.getTransactionDebitCreditCode())) {
reversal.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
UniversityDate udate = SpringContext.getBean(BusinessObjectService.class).findBySinglePrimaryKey(UniversityDate.class, reversal.getFinancialDocumentReversalDate());
if (udate != null) {
reversal.setUniversityFiscalYear(udate.getUniversityFiscalYear());
reversal.setUniversityFiscalPeriodCode(udate.getUniversityFiscalAccountingPeriod());
AccountingPeriod ap = accountingPeriodService.getByPeriod(reversal.getUniversityFiscalPeriodCode(), reversal.getUniversityFiscalYear());
if (ap != null) {
if (!ap.isActive()) { // Make sure accounting period is closed
reversal.setUniversityFiscalYear(runUniversityDate.getUniversityFiscalYear());
reversal.setUniversityFiscalPeriodCode(runUniversityDate.getUniversityFiscalAccountingPeriod());
}
reversal.setFinancialDocumentReversalDate(null);
String newDescription = KFSConstants.GL_REVERSAL_DESCRIPTION_PREFIX + reversal.getTransactionLedgerEntryDescription();
if (newDescription.length() > 40) {
newDescription = newDescription.substring(0, 40);
}
reversal.setTransactionLedgerEntryDescription(newDescription);
}
else {
errors.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_UNIV_DATE_NOT_IN_ACCOUNTING_PERIOD_TABLE), Message.TYPE_WARNING));
}
}
else {
errors.add(new Message (configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_REVERSAL_DATE_NOT_IN_UNIV_DATE_TABLE) , Message.TYPE_WARNING));
}
// Make sure the row will be unique when adding to the entries table by adjusting the transaction sequence id
int maxSequenceId = accountingCycleCachingService.getMaxSequenceNumber(reversal);
reversal.setTransactionLedgerEntrySequenceNumber(new Integer(maxSequenceId + 1));
PersistenceService ps = SpringContext.getBean(PersistenceService.class);
ps.retrieveNonKeyFields(reversal);
tran = reversal;
}
else {
tran.setChart(accountingCycleCachingService.getChart(tran.getChartOfAccountsCode()));
tran.setAccount(accountingCycleCachingService.getAccount(tran.getChartOfAccountsCode(), tran.getAccountNumber()));
tran.setObjectType(accountingCycleCachingService.getObjectType(tran.getFinancialObjectTypeCode()));
tran.setBalanceType(accountingCycleCachingService.getBalanceType(tran.getFinancialBalanceTypeCode()));
tran.setOption(accountingCycleCachingService.getSystemOptions(tran.getUniversityFiscalYear()));
ObjectCode objectCode = accountingCycleCachingService.getObjectCode(tran.getUniversityFiscalYear(), tran.getChartOfAccountsCode(), tran.getFinancialObjectCode());
if (ObjectUtils.isNull(objectCode)) {
LOG.warn(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + tran.getUniversityFiscalYear() + "," + tran.getChartOfAccountsCode() + "," + tran.getFinancialObjectCode());
errors.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + tran.getUniversityFiscalYear() + "," + tran.getChartOfAccountsCode() + "," + tran.getFinancialObjectCode(), Message.TYPE_WARNING));
}
else {
tran.setFinancialObject(accountingCycleCachingService.getObjectCode(tran.getUniversityFiscalYear(), tran.getChartOfAccountsCode(), tran.getFinancialObjectCode()));
}
// Make sure the row will be unique when adding to the entries table by adjusting the transaction sequence id
int maxSequenceId = accountingCycleCachingService.getMaxSequenceNumber(tran);
((OriginEntryFull) tran).setTransactionLedgerEntrySequenceNumber(new Integer(maxSequenceId + 1));
}
// verify accounting period
AccountingPeriod originEntryAccountingPeriod = accountingCycleCachingService.getAccountingPeriod(tran.getUniversityFiscalYear(), tran.getUniversityFiscalPeriodCode());
if (originEntryAccountingPeriod == null) {
errors.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_ACCOUNTING_PERIOD_NOT_FOUND) + " for " + tran.getUniversityFiscalYear() + "/" + tran.getUniversityFiscalPeriodCode(), Message.TYPE_FATAL));
}
if (errors.size() == 0) {
try {
errors = verifyTransaction.verifyTransaction(tran);
}
catch (Exception e) {
errors.add(new Message(e.toString() + " occurred for this record.", Message.TYPE_FATAL));
}
}
if (errors.size() > 0) {
// Error on this transaction
reportWriterService.writeError(tran, errors);
addReporting(reportSummary, "WARNING", GeneralLedgerConstants.INSERT_CODE);
try {
writeErrorEntry(line, invalidGroup);
}
catch (IOException ioe) {
LOG.error("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
throw new RuntimeException("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
}
}
else {
// No error so post it
for (Iterator posterIter = transactionPosters.iterator(); posterIter.hasNext();) {
PostTransaction poster = (PostTransaction) posterIter.next();
String actionCode = poster.post(tran, mode, runUniversityDate.getUniversityDate(), reportWriterService);
if (actionCode.startsWith(GeneralLedgerConstants.ERROR_CODE)) {
errors = new ArrayList<Message>();
errors.add(new Message(actionCode, Message.TYPE_WARNING));
reportWriterService.writeError(tran, errors);
}
else if (actionCode.indexOf(GeneralLedgerConstants.INSERT_CODE) >= 0) {
addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.INSERT_CODE);
}
else if (actionCode.indexOf(GeneralLedgerConstants.UPDATE_CODE) >= 0) {
addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.UPDATE_CODE);
}
else if (actionCode.indexOf(GeneralLedgerConstants.DELETE_CODE) >= 0) {
addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.DELETE_CODE);
}
else if (actionCode.indexOf(GeneralLedgerConstants.SELECT_CODE) >= 0) {
addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.SELECT_CODE);
}
}
if (errors.size() == 0) {
// Delete the reversal entry
if (mode == PosterService.MODE_REVERSAL) {
createOutputEntry(tran, OUTPUT_GLE_FILE_ps);
reversalDao.delete((Reversal) originalTransaction);
addReporting(reportSummary, getPersistenceStructureService().getTableName(Reversal.class), GeneralLedgerConstants.DELETE_CODE);
}
ledgerSummaryReport.summarizeEntry(new OriginEntryFull(tran));
return true;
}
}
return false;
}
catch (IOException ioe) {
LOG.error("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
throw new RuntimeException("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
}
catch (RuntimeException re) {
LOG.error("PosterServiceImpl Stopped: " + re.getMessage(), re);
throw new RuntimeException("PosterServiceImpl Stopped: " + re.getMessage(), re);
}
}
/**
* This step reads the expenditure table and uses the data to generate Indirect Cost Recovery transactions.
*/
@Override
public void generateIcrTransactions() {
LOG.debug("generateIcrTransactions() started");
Date executionDate = dateTimeService.getCurrentSqlDate();
Date runDate = new Date(runDateService.calculateRunDate(executionDate).getTime());
try {
PrintStream OUTPUT_GLE_FILE_ps = new PrintStream(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_TRANSACTIONS_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
int reportExpendTranRetrieved = 0;
int reportExpendTranDeleted = 0;
int reportExpendTranKept = 0;
int reportOriginEntryGenerated = 0;
Iterator expenditureTransactions;
try {
expenditureTransactions = expenditureTransactionDao.getAllExpenditureTransactions();
}
catch (RuntimeException re) {
LOG.error("generateIcrTransactions Stopped: " + re.getMessage());
throw new RuntimeException("generateIcrTransactions Stopped: " + re.getMessage(), re);
}
while (expenditureTransactions.hasNext()) {
ExpenditureTransaction et = new ExpenditureTransaction();
try {
et = (ExpenditureTransaction) expenditureTransactions.next();
reportExpendTranRetrieved++;
KualiDecimal transactionAmount = et.getAccountObjectDirectCostAmount();
KualiDecimal distributionAmount = KualiDecimal.ZERO;
if (shouldIgnoreExpenditureTransaction(et)) {
// Delete expenditure record
expenditureTransactionDao.delete(et);
reportExpendTranDeleted++;
continue;
}
IndirectCostRecoveryGenerationMetadata icrGenerationMetadata = retrieveSubAccountIndirectCostRecoveryMetadata(et);
if (icrGenerationMetadata == null) {
// ICR information was not set up properly for sub-account, default to using ICR information from the account
icrGenerationMetadata = retrieveAccountIndirectCostRecoveryMetadata(et);
}
Collection<IndirectCostRecoveryRateDetail> automatedEntries = indirectCostRecoveryRateDetailDao.getActiveRateDetailsByRate(et.getUniversityFiscalYear(), icrGenerationMetadata.getFinancialIcrSeriesIdentifier());
int automatedEntriesCount = automatedEntries.size();
if (automatedEntriesCount > 0) {
for (Iterator icrIter = automatedEntries.iterator(); icrIter.hasNext();) {
IndirectCostRecoveryRateDetail icrEntry = (IndirectCostRecoveryRateDetail) icrIter.next();
KualiDecimal generatedTransactionAmount = null;
if (!icrIter.hasNext()) {
generatedTransactionAmount = distributionAmount;
// Log differences that are over WARNING_MAX_DIFFERENCE
if (getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct()).subtract(distributionAmount).abs().isGreaterThan(WARNING_MAX_DIFFERENCE)) {
List<Message> warnings = new ArrayList<Message>();
warnings.add(new Message("ADJUSTMENT GREATER THAN " + WARNING_MAX_DIFFERENCE, Message.TYPE_WARNING));
reportWriterService.writeError(et, warnings);
}
}
else if (icrEntry.getTransactionDebitIndicator().equals(KFSConstants.GL_DEBIT_CODE)) {
generatedTransactionAmount = getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct());
distributionAmount = distributionAmount.add(generatedTransactionAmount);
}
else if (icrEntry.getTransactionDebitIndicator().equals(KFSConstants.GL_CREDIT_CODE)) {
generatedTransactionAmount = getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct());
distributionAmount = distributionAmount.subtract(generatedTransactionAmount);
}
else {
// Log if D / C code not found
List<Message> warnings = new ArrayList<Message>();
warnings.add(new Message("DEBIT OR CREDIT CODE NOT FOUND", Message.TYPE_FATAL));
reportWriterService.writeError(et, warnings);
}
//KFSMI-5614 CHANGED
generateTransactionsBySymbol(et, icrEntry, generatedTransactionAmount, runDate, OUTPUT_GLE_FILE_ps, icrGenerationMetadata);
reportOriginEntryGenerated = reportOriginEntryGenerated + 2;
}
}
// Delete expenditure record
expenditureTransactionDao.delete(et);
reportExpendTranDeleted++;
}
catch (RuntimeException re) {
LOG.error("generateIcrTransactions Stopped: " + re.getMessage());
throw new RuntimeException("generateIcrTransactions Stopped: " + re.getMessage(), re);
}
catch (Exception e) {
List errorList = new ArrayList();
errorList.add(new Message(e.toString() + " occurred for this record.", Message.TYPE_FATAL));
reportWriterService.writeError(et, errorList);
}
}
OUTPUT_GLE_FILE_ps.close();
reportWriterService.writeStatisticLine("GLEX RECORDS READ (GL_EXPEND_TRN_MT) %,9d", reportExpendTranRetrieved);
reportWriterService.writeStatisticLine("GLEX RECORDS DELETED (GL_EXPEND_TRN_MT) %,9d", reportExpendTranDeleted);
reportWriterService.writeStatisticLine("GLEX RECORDS KEPT DUE TO ERRORS (GL_EXPEND_TRN_MT) %,9d", reportExpendTranKept);
reportWriterService.writeStatisticLine("TRANSACTIONS GENERATED %,9d", reportOriginEntryGenerated);
}
catch (FileNotFoundException e) {
throw new RuntimeException("generateIcrTransactions Stopped: " + e.getMessage(), e);
}
}
/**
* Wrapper function to allow for internal iterations on ICR account distribution collection if determined to use ICR from account
*
* @param et
* @param icrRateDetail
* @param generatedTransactionAmount
* @param runDate
* @param group
* @param icrGenerationMetadata
*/
private void generateTransactionsBySymbol(ExpenditureTransaction et, IndirectCostRecoveryRateDetail icrRateDetail, KualiDecimal generatedTransactionAmount, Date runDate, PrintStream group, IndirectCostRecoveryGenerationMetadata icrGenerationMetadata) {
KualiDecimal icrTransactionAmount;
KualiDecimal unappliedTransactionAmount = new KualiDecimal(generatedTransactionAmount.bigDecimalValue());
//if symbol is denoted to use ICR from account
if (GeneralLedgerConstants.PosterService.SYMBOL_USE_ICR_FROM_ACCOUNT.equals(icrRateDetail.getAccountNumber())) {
int icrCount = icrGenerationMetadata.getAccountLists().size();
for (IndirectCostRecoveryAccountDistributionMetadata meta : icrGenerationMetadata.getAccountLists()){
//set a new icr meta data for transaction processing
IndirectCostRecoveryGenerationMetadata icrMeta = new IndirectCostRecoveryGenerationMetadata(icrGenerationMetadata.getIndirectCostRecoveryTypeCode(),
icrGenerationMetadata.getFinancialIcrSeriesIdentifier());
icrMeta.setIndirectCostRcvyFinCoaCode(meta.getIndirectCostRecoveryFinCoaCode());
icrMeta.setIndirectCostRecoveryAcctNbr(meta.getIndirectCostRecoveryAccountNumber());
//change the transaction amount base on ICR percentage
if (icrCount-- == 1) {
// Deplete the rest of un-applied transaction amount
icrTransactionAmount = unappliedTransactionAmount;
}
else {
// Normal transaction amount is calculated by icr account line percentage
icrTransactionAmount = getPercentage(generatedTransactionAmount, meta.getAccountLinePercent());
unappliedTransactionAmount = unappliedTransactionAmount.subtract(icrTransactionAmount);
}
//perform the actual transaction generation
generateTransactions(et, icrRateDetail, icrTransactionAmount, runDate, group, icrMeta);
}
}else{
//non-ICR; process as usual
generateTransactions(et, icrRateDetail, generatedTransactionAmount, runDate, group, icrGenerationMetadata);
}
}
/**
* Generate a transfer transaction and an offset transaction
*
* @param et an expenditure transaction
* @param icrEntry the indirect cost recovery entry
* @param generatedTransactionAmount the amount of the transaction
* @param runDate the transaction date for the newly created origin entry
* @param group the group to save the origin entry to
*/
protected void generateTransactions(ExpenditureTransaction et, IndirectCostRecoveryRateDetail icrRateDetail, KualiDecimal generatedTransactionAmount, Date runDate, PrintStream group, IndirectCostRecoveryGenerationMetadata icrGenerationMetadata) {
BigDecimal pct = new BigDecimal(icrRateDetail.getAwardIndrCostRcvyRatePct().toString());
pct = pct.divide(BDONEHUNDRED);
OriginEntryFull e = new OriginEntryFull();
e.setTransactionLedgerEntrySequenceNumber(0);
// SYMBOL_USE_EXPENDITURE_ENTRY means we use the field from the expenditure entry, SYMBOL_USE_IRC_FROM_ACCOUNT
// means we use the ICR field from the account record, otherwise, use the field in the icrRateDetail
if (GeneralLedgerConstants.PosterService.SYMBOL_USE_EXPENDITURE_ENTRY.equals(icrRateDetail.getFinancialObjectCode()) || GeneralLedgerConstants.PosterService.SYMBOL_USE_ICR_FROM_ACCOUNT.equals(icrRateDetail.getFinancialObjectCode())) {
e.setFinancialObjectCode(et.getObjectCode());
e.setFinancialSubObjectCode(et.getSubObjectCode());
}
else {
e.setFinancialObjectCode(icrRateDetail.getFinancialObjectCode());
e.setFinancialSubObjectCode(icrRateDetail.getFinancialSubObjectCode());
}
if (GeneralLedgerConstants.PosterService.SYMBOL_USE_EXPENDITURE_ENTRY.equals(icrRateDetail.getAccountNumber())) {
e.setAccountNumber(et.getAccountNumber());
e.setChartOfAccountsCode(et.getChartOfAccountsCode());
e.setSubAccountNumber(et.getSubAccountNumber());
}
else if (GeneralLedgerConstants.PosterService.SYMBOL_USE_ICR_FROM_ACCOUNT.equals(icrRateDetail.getAccountNumber())) {
e.setAccountNumber(icrGenerationMetadata.getIndirectCostRecoveryAcctNbr());
e.setChartOfAccountsCode(icrGenerationMetadata.getIndirectCostRcvyFinCoaCode());
e.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
}
else {
e.setAccountNumber(icrRateDetail.getAccountNumber());
e.setSubAccountNumber(icrRateDetail.getSubAccountNumber());
e.setChartOfAccountsCode(icrRateDetail.getChartOfAccountsCode());
// TODO Reporting thing line 1946
}
// take care of infinite recursive error case - do not generate entries
if ((et.getAccountNumber().equals(e.getAccountNumber() )) &&
( et.getChartOfAccountsCode().equals(e.getChartOfAccountsCode())) &&
(et.getSubAccountNumber().equals(e.getSubAccountNumber())) &&
(et.getObjectCode().equals(e.getFinancialObjectCode())) &&
(et.getSubObjectCode().equals(e.getFinancialSubObjectCode()))) {
List<Message> warnings = new ArrayList<Message>();
warnings.add(new Message("Infinite recursive encumbrance error " + et.getChartOfAccountsCode() + " " + et.getAccountNumber() + " " + et.getSubAccountNumber() + " " + et.getObjectCode() + " " + et.getSubObjectCode(), Message.TYPE_WARNING));
reportWriterService.writeError(et, warnings);
return;
}
e.setFinancialDocumentTypeCode(parameterService.getParameterValueAsString(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY));
e.setFinancialSystemOriginationCode(parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ORIGINATION_CODE));
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_STRING);
e.setDocumentNumber(sdf.format(runDate));
if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
e.setTransactionLedgerEntryDescription(getChargeDescription(pct, et.getObjectCode(), icrGenerationMetadata.getIndirectCostRecoveryTypeCode(), et.getAccountObjectDirectCostAmount().abs()));
}
else {
e.setTransactionLedgerEntryDescription(getOffsetDescription(pct, et.getAccountObjectDirectCostAmount().abs(), et.getChartOfAccountsCode(), et.getAccountNumber()));
}
e.setTransactionDate(new java.sql.Date(runDate.getTime()));
e.setTransactionDebitCreditCode(icrRateDetail.getTransactionDebitIndicator());
e.setFinancialBalanceTypeCode(et.getBalanceTypeCode());
e.setUniversityFiscalYear(et.getUniversityFiscalYear());
e.setUniversityFiscalPeriodCode(et.getUniversityFiscalAccountingPeriod());
ObjectCode oc = objectCodeService.getByPrimaryId(e.getUniversityFiscalYear(), e.getChartOfAccountsCode(), e.getFinancialObjectCode());
if (oc == null) {
LOG.warn(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + e.getUniversityFiscalYear() + "," + e.getChartOfAccountsCode() + "," + e.getFinancialObjectCode());
e.setFinancialObjectCode(icrRateDetail.getFinancialObjectCode()); // this will be written out the ICR file. Then, when that file attempts to post, the transaction won't validate and will end up in the icr error file
} else {
e.setFinancialObjectTypeCode(oc.getFinancialObjectTypeCode());
}
if (generatedTransactionAmount.isNegative()) {
if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
e.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else {
e.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
e.setTransactionLedgerEntryAmount(generatedTransactionAmount.negated());
}
else {
e.setTransactionLedgerEntryAmount(generatedTransactionAmount);
}
if (et.getBalanceTypeCode().equals(et.getOption().getExtrnlEncumFinBalanceTypCd()) || et.getBalanceTypeCode().equals(et.getOption().getIntrnlEncumFinBalanceTypCd()) || et.getBalanceTypeCode().equals(et.getOption().getPreencumbranceFinBalTypeCd()) || et.getBalanceTypeCode().equals(et.getOption().getCostShareEncumbranceBalanceTypeCd())) {
e.setDocumentNumber(parameterService.getParameterValueAsString(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY));
}
e.setProjectCode(et.getProjectCode());
if (GeneralLedgerConstants.getDashOrganizationReferenceId().equals(et.getOrganizationReferenceId())) {
e.setOrganizationReferenceId(null);
}
else {
e.setOrganizationReferenceId(et.getOrganizationReferenceId());
}
// TODO 2031-2039
try {
createOutputEntry(e, group);
}
catch (IOException ioe) {
LOG.error("generateTransactions Stopped: " + ioe.getMessage());
throw new RuntimeException("generateTransactions Stopped: " + ioe.getMessage(), ioe);
}
// Now generate Offset
e = new OriginEntryFull(e);
if (KFSConstants.GL_DEBIT_CODE.equals(e.getTransactionDebitCreditCode())) {
e.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else {
e.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
e.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
String offsetBalanceSheetObjectCodeNumber = determineIcrOffsetBalanceSheetObjectCodeNumber(e, et, icrRateDetail);
e.setFinancialObjectCode(offsetBalanceSheetObjectCodeNumber);
ObjectCode balSheetObjectCode = objectCodeService.getByPrimaryId(icrRateDetail.getUniversityFiscalYear(), e.getChartOfAccountsCode(), offsetBalanceSheetObjectCodeNumber);
if (balSheetObjectCode == null) {
List<Message> warnings = new ArrayList<Message>();
warnings.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_INVALID_OFFSET_OBJECT_CODE) + icrRateDetail.getUniversityFiscalYear() + "-" + e.getChartOfAccountsCode() + "-" +offsetBalanceSheetObjectCodeNumber, Message.TYPE_WARNING));
reportWriterService.writeError(et, warnings);
}
else {
e.setFinancialObjectTypeCode(balSheetObjectCode.getFinancialObjectTypeCode());
}
if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
e.setTransactionLedgerEntryDescription(getChargeDescription(pct, et.getObjectCode(), icrGenerationMetadata.getIndirectCostRecoveryTypeCode(), et.getAccountObjectDirectCostAmount().abs()));
}
else {
e.setTransactionLedgerEntryDescription(getOffsetDescription(pct, et.getAccountObjectDirectCostAmount().abs(), et.getChartOfAccountsCode(), et.getAccountNumber()));
}
try {
flexibleOffsetAccountService.updateOffset(e);
}
catch (InvalidFlexibleOffsetException ex) {
List<Message> warnings = new ArrayList<Message>();
warnings.add(new Message("FAILED TO GENERATE FLEXIBLE OFFSETS " + ex.getMessage(), Message.TYPE_WARNING));
reportWriterService.writeError(et, warnings);
LOG.warn("FAILED TO GENERATE FLEXIBLE OFFSETS FOR EXPENDITURE TRANSACTION " + et.toString(), ex);
}
try {
createOutputEntry(e, group);
}
catch (IOException ioe) {
LOG.error("generateTransactions Stopped: " + ioe.getMessage());
throw new RuntimeException("generateTransactions Stopped: " + ioe.getMessage(), ioe);
}
}
public final static KualiDecimal ONEHUNDRED = new KualiDecimal("100");
public final static DecimalFormat DFPCT = new DecimalFormat("#0.000");
public final static DecimalFormat DFAMT = new DecimalFormat("##########.00");
public final static BigDecimal BDONEHUNDRED = new BigDecimal("100");
/**
* Returns ICR Generation Metadata based on SubAccount information if the SubAccount on the expenditure transaction is properly
* set up for ICR
*
* @param et
* @param reportErrors
* @return null if the ET does not have a SubAccount properly set up for ICR
*/
protected IndirectCostRecoveryGenerationMetadata retrieveSubAccountIndirectCostRecoveryMetadata(ExpenditureTransaction et) {
SubAccount subAccount = accountingCycleCachingService.getSubAccount(et.getChartOfAccountsCode(), et.getAccountNumber(), et.getSubAccountNumber());
if (ObjectUtils.isNotNull(subAccount)) {
subAccount.setA21SubAccount(accountingCycleCachingService.getA21SubAccount(et.getChartOfAccountsCode(), et.getAccountNumber(), et.getSubAccountNumber()));
}
if (ObjectUtils.isNotNull(subAccount) && ObjectUtils.isNotNull(subAccount.getA21SubAccount())) {
A21SubAccount a21SubAccount = subAccount.getA21SubAccount();
List<A21IndirectCostRecoveryAccount> activeICRAccounts = a21SubAccount.getA21ActiveIndirectCostRecoveryAccounts();
if (StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryTypeCode()) && StringUtils.isBlank(a21SubAccount.getFinancialIcrSeriesIdentifier()) && activeICRAccounts.isEmpty()) {
// all ICR fields were blank, therefore, this sub account was not set up for ICR
return null;
}
// refresh the indirect cost recovery account, accounting cycle style!
Account refreshSubAccount = null;
if (!StringUtils.isBlank(a21SubAccount.getChartOfAccountsCode()) && !StringUtils.isBlank(a21SubAccount.getAccountNumber())) {
refreshSubAccount = accountingCycleCachingService.getAccount(a21SubAccount.getChartOfAccountsCode(), a21SubAccount.getAccountNumber());
}
// these fields will be used to construct warning messages
String warningMessagePattern = configurationService.getPropertyValueAsString(KFSKeyConstants.WARNING_ICR_GENERATION_PROBLEM_WITH_A21SUBACCOUNT_FIELD_BLANK_INVALID);
String subAccountBOLabel = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(SubAccount.class.getName()).getObjectLabel();
String subAccountValue = subAccount.getChartOfAccountsCode() + "-" + subAccount.getAccountNumber() + "-" + subAccount.getSubAccountNumber();
String accountBOLabel = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Account.class.getName()).getObjectLabel();
String accountValue = et.getChartOfAccountsCode() + "-" + et.getAccountNumber();
boolean subAccountOK = true;
// there were some ICR fields that were filled in, make sure they're all filled in and are valid values
a21SubAccount.setIndirectCostRecoveryType(accountingCycleCachingService.getIndirectCostRecoveryType(a21SubAccount.getIndirectCostRecoveryTypeCode()));
if (StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryTypeCode()) || ObjectUtils.isNull(a21SubAccount.getIndirectCostRecoveryType())) {
String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.INDIRECT_COST_RECOVERY_TYPE_CODE);
String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
subAccountOK = false;
}
if (StringUtils.isBlank(a21SubAccount.getFinancialIcrSeriesIdentifier())) {
Map<String, Object> icrRatePkMap = new HashMap<String, Object>();
icrRatePkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, et.getUniversityFiscalYear());
icrRatePkMap.put(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, a21SubAccount.getFinancialIcrSeriesIdentifier());
IndirectCostRecoveryRate indirectCostRecoveryRate = businessObjectService.findByPrimaryKey(IndirectCostRecoveryRate.class, icrRatePkMap);
if (indirectCostRecoveryRate == null) {
String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER);
String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
subAccountOK = false;
}
}
if (activeICRAccounts.isEmpty() || ObjectUtils.isNull(refreshSubAccount)) {
String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21IndirectCostRecoveryAccount.class, KFSPropertyConstants.ICR_CHART_OF_ACCOUNTS_CODE) + "/" + dataDictionaryService.getAttributeShortLabel(A21IndirectCostRecoveryAccount.class, KFSPropertyConstants.ICR_ACCOUNT_NUMBER);
String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
subAccountOK = false;
}
if (subAccountOK) {
IndirectCostRecoveryGenerationMetadata metadata = new IndirectCostRecoveryGenerationMetadata(a21SubAccount.getIndirectCostRecoveryTypeCode(),
a21SubAccount.getFinancialIcrSeriesIdentifier());
List<IndirectCostRecoveryAccountDistributionMetadata> icrAccountList = metadata.getAccountLists();
for (A21IndirectCostRecoveryAccount a21 : activeICRAccounts){
icrAccountList.add(new IndirectCostRecoveryAccountDistributionMetadata(a21));
}
return metadata;
}
}
return null;
}
protected IndirectCostRecoveryGenerationMetadata retrieveAccountIndirectCostRecoveryMetadata(ExpenditureTransaction et) {
Account account = et.getAccount();
IndirectCostRecoveryGenerationMetadata metadata = new IndirectCostRecoveryGenerationMetadata(account.getAcctIndirectCostRcvyTypeCd(),
account.getFinancialIcrSeriesIdentifier());
List<IndirectCostRecoveryAccountDistributionMetadata> icrAccountList = metadata.getAccountLists();
for (IndirectCostRecoveryAccount icr : account.getActiveIndirectCostRecoveryAccounts()){
icrAccountList.add(new IndirectCostRecoveryAccountDistributionMetadata(icr));
}
return metadata;
}
/**
* Generates a percent of a KualiDecimal amount (great for finding out how much of an origin entry should be recouped by
* indirect cost recovery)
*
* @param amount the original amount
* @param percent the percentage of that amount to calculate
* @return the percent of the amount
*/
protected KualiDecimal getPercentage(KualiDecimal amount, BigDecimal percent) {
BigDecimal result = amount.bigDecimalValue().multiply(percent).divide(BDONEHUNDRED, 2, BigDecimal.ROUND_DOWN);
return new KualiDecimal(result);
}
/**
* Generates the description of a charge
*
* @param rate the ICR rate for this entry
* @param objectCode the object code of this entry
* @param type the ICR type code of this entry's account
* @param amount the amount of this entry
* @return a description for the charge entry
*/
protected String getChargeDescription(BigDecimal rate, String objectCode, String type, KualiDecimal amount) {
BigDecimal newRate = rate.multiply(PosterServiceImpl.BDONEHUNDRED);
StringBuffer desc = new StringBuffer("CHG ");
if (newRate.doubleValue() < 10) {
desc.append(" ");
}
desc.append(DFPCT.format(newRate));
desc.append("% ON ");
desc.append(objectCode);
desc.append(" (");
desc.append(type);
desc.append(") ");
String amt = DFAMT.format(amount);
while (amt.length() < 13) {
amt = " " + amt;
}
desc.append(amt);
return desc.toString();
}
/**
* Returns the description of a debit origin entry created by generateTransactions
*
* @param rate the ICR rate that relates to this entry
* @param amount the amount of this entry
* @param chartOfAccountsCode the chart codce of the debit entry
* @param accountNumber the account number of the debit entry
* @return a description for the debit entry
*/
protected String getOffsetDescription(BigDecimal rate, KualiDecimal amount, String chartOfAccountsCode, String accountNumber) {
BigDecimal newRate = rate.multiply(PosterServiceImpl.BDONEHUNDRED);
StringBuffer desc = new StringBuffer("RCV ");
if (newRate.doubleValue() < 10) {
desc.append(" ");
}
desc.append(DFPCT.format(newRate));
desc.append("% ON ");
String amt = DFAMT.format(amount);
while (amt.length() < 13) {
amt = " " + amt;
}
desc.append(amt);
desc.append(" FRM ");
// desc.append(chartOfAccountsCode);
// desc.append("-");
desc.append(accountNumber);
return desc.toString();
}
/**
* Increments a named count holding statistics about posted transactions
*
* @param reporting a Map of counts generated by this process
* @param destination the destination of a given transaction
* @param operation the operation being performed on the transaction
*/
protected void addReporting(Map reporting, String destination, String operation) {
String key = destination + "," + operation;
//TODO: remove this if block. Added to troubleshoot FSKD-194.
if("GL_EXPEND_TRN_MT".equals(destination)){
LOG.info("Counting GLEX operation: "+operation);
}
if (reporting.containsKey(key)) {
Integer c = (Integer) reporting.get(key);
reporting.put(key, new Integer(c.intValue() + 1));
}
else {
reporting.put(key, new Integer(1));
}
}
protected String determineIcrOffsetBalanceSheetObjectCodeNumber(OriginEntryInformation offsetEntry, ExpenditureTransaction et, IndirectCostRecoveryRateDetail icrRateDetail) {
String icrEntryDocumentType = parameterService.getParameterValueAsString(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY);
OffsetDefinition offsetDefinition = offsetDefinitionService.getByPrimaryId(offsetEntry.getUniversityFiscalYear(), offsetEntry.getChartOfAccountsCode(), icrEntryDocumentType, et.getBalanceTypeCode());
if (!ObjectUtils.isNull(offsetDefinition)) {
return offsetDefinition.getFinancialObjectCode();
} else {
return null;
}
}
/**
* The purpose of this method is to build and post the offset transaction. It does this by performing the following steps:
* 1. Getting the offset object code from the GL Offset Definition Table.
* 2. For the offset object code it needs to get the associated object type.
* 3. Flip the transaction debit credit code
* 4. Post the offset transaction.
*
* @param tran a transaction to post
* @param mode the mode the poster is running in
* @param reportSummary a Map of summary counts generated by the posting process
* @param ledgerSummaryReport for summary reporting
* @param invalidGroup the group to save invalid entries to
* @param runUniversityDate the university date of this poster run
* @param line the input line used for printing
* @return true if an offset would be needed for this entry, false otherwise
*/
protected boolean generateOffset(OriginEntryFull tran, int mode, Map<String,Integer> reportSummary, LedgerSummaryReport ledgerSummaryReport, PrintStream invalidGroup, UniversityDate runUniversityDate, String line, PrintStream OUTPUT_GLE_FILE_ps) {
if (LOG.isDebugEnabled()) {
LOG.debug("generateOffset() started");
}
List<Message> errors = new ArrayList();
OriginEntryFull offsetEntry = new OriginEntryFull(tran);
String offsetDescription = configurationService.getPropertyValueAsString(KFSKeyConstants.MSG_GENERATED_OFFSET);
offsetEntry.setTransactionLedgerEntryDescription(offsetDescription);
OffsetDefinition offsetDefinition = offsetDefinitionService.getByPrimaryId(offsetEntry.getUniversityFiscalYear(), offsetEntry.getChartOfAccountsCode(), offsetEntry.getFinancialDocumentTypeCode(), offsetEntry.getFinancialBalanceTypeCode());
if (!ObjectUtils.isNull(offsetDefinition)) {
if (offsetDefinition.getFinancialObject() == null) {
errors.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_OBJECT_CODE_NOT_FOUND), Message.TYPE_WARNING));
}
offsetEntry.setFinancialObject(offsetDefinition.getFinancialObject());
offsetEntry.setFinancialObjectCode(offsetDefinition.getFinancialObjectCode());
offsetEntry.setFinancialSubObject(null);
offsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
offsetEntry.setFinancialObjectTypeCode(offsetEntry.getFinancialObject().getFinancialObjectTypeCode());
}
else {
errors.add(new Message(configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_NOT_FOUND), Message.TYPE_WARNING));
}
if (KFSConstants.GL_DEBIT_CODE.equals(offsetEntry.getTransactionDebitCreditCode())) {
offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
}
else {
offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
}
try {
flexibleOffsetAccountService.updateOffset(offsetEntry);
}
catch (InvalidFlexibleOffsetException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("generateOffset() Offset Flexible Offset Error: " + e.getMessage());
}
errors.add(new Message("FAILED TO GENERATE FLEXIBLE OFFSETS " + e.getMessage(), Message.TYPE_WARNING));
}
if (errors.size() > 0) {
reportWriterService.writeError(offsetEntry, errors);
addReporting(reportSummary, "WARNING", GeneralLedgerConstants.INSERT_CODE);
}
//update input record from offset entry for error reporting
line = offsetEntry.getLine();
postTransaction(offsetEntry, mode, reportSummary, ledgerSummaryReport, invalidGroup, runUniversityDate, line, OUTPUT_GLE_FILE_ps);
return true;
}
public void setVerifyTransaction(VerifyTransaction vt) {
verifyTransaction = vt;
}
public void setTransactionPosters(List p) {
transactionPosters = p;
}
public void setOriginEntryService(OriginEntryService oes) {
originEntryService = oes;
}
public void setOriginEntryGroupService(OriginEntryGroupService oes) {
originEntryGroupService = oes;
}
@Override
public void setDateTimeService(DateTimeService dts) {
dateTimeService = dts;
}
public void setReversalDao(ReversalDao red) {
reversalDao = red;
}
public void setUniversityDateDao(UniversityDateDao udd) {
universityDateDao = udd;
}
public void setAccountingPeriodService(AccountingPeriodService aps) {
accountingPeriodService = aps;
}
public void setExpenditureTransactionDao(ExpenditureTransactionDao etd) {
expenditureTransactionDao = etd;
}
public void setIndirectCostRecoveryRateDetailDao(IndirectCostRecoveryRateDetailDao iaed) {
indirectCostRecoveryRateDetailDao = iaed;
}
public void setObjectCodeService(ObjectCodeService ocs) {
objectCodeService = ocs;
}
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
public void setFlexibleOffsetAccountService(FlexibleOffsetAccountService flexibleOffsetAccountService) {
this.flexibleOffsetAccountService = flexibleOffsetAccountService;
}
public RunDateService getRunDateService() {
return runDateService;
}
public void setRunDateService(RunDateService runDateService) {
this.runDateService = runDateService;
}
protected void createOutputEntry(Transaction entry, PrintStream group) throws IOException {
OriginEntryFull oef = new OriginEntryFull();
oef.copyFieldsFromTransaction(entry);
try {
group.printf("%s\n", oef.getLine());
}
catch (Exception e) {
throw new IOException(e.toString());
}
}
protected void writeErrorEntry(String line, PrintStream invaliGroup) throws IOException {
try {
invaliGroup.printf("%s\n", line);
} catch (Exception e) {
throw new IOException(e.toString());
}
}
public AccountingCycleCachingService getAccountingCycleCachingService() {
return accountingCycleCachingService;
}
public void setAccountingCycleCachingService(AccountingCycleCachingService accountingCycleCachingService) {
this.accountingCycleCachingService = accountingCycleCachingService;
}
public void setSubAccountService(SubAccountService subAccountService) {
this.subAccountService = subAccountService;
}
public void setOffsetDefinitionService(OffsetDefinitionService offsetDefinitionService) {
this.offsetDefinitionService = offsetDefinitionService;
}
protected DataDictionaryService getDataDictionaryService() {
return dataDictionaryService;
}
public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
this.dataDictionaryService = dataDictionaryService;
}
protected BusinessObjectService getBusinessObjectService() {
return businessObjectService;
}
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
protected boolean shouldIgnoreExpenditureTransaction(ExpenditureTransaction et) {
if (ObjectUtils.isNotNull(et.getOption())) {
SystemOptions options = et.getOption();
return StringUtils.isNotBlank(options.getActualFinancialBalanceTypeCd()) && !options.getActualFinancialBalanceTypeCd().equals(et.getBalanceTypeCode());
}
return true;
}
public void setBatchFileDirectoryName(String batchFileDirectoryName) {
this.batchFileDirectoryName = batchFileDirectoryName;
}
public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
this.persistenceStructureService = persistenceStructureService;
}
/**
* Gets the persistenceStructureService attribute.
* @return Returns the persistenceStructureService.
*/
public PersistenceStructureService getPersistenceStructureService() {
return persistenceStructureService;
}
public void setReportWriterService(ReportWriterService reportWriterService) {
this.reportWriterService = reportWriterService;
}
public void setErrorListingReportWriterService(ReportWriterService errorListingReportWriterService) {
this.errorListingReportWriterService = errorListingReportWriterService;
}
public void setReversalReportWriterService(ReportWriterService reversalReportWriterService) {
this.reversalReportWriterService = reversalReportWriterService;
}
public void setLedgerSummaryReportWriterService(ReportWriterService ledgerSummaryReportWriterService) {
this.ledgerSummaryReportWriterService = ledgerSummaryReportWriterService;
}
}