001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.kuali.kfs.gl.batch.service.impl;
018    
019    import java.io.BufferedReader;
020    import java.io.File;
021    import java.io.FileNotFoundException;
022    import java.io.FileReader;
023    import java.io.IOException;
024    import java.io.PrintStream;
025    import java.math.BigDecimal;
026    import java.sql.Date;
027    import java.text.DecimalFormat;
028    import java.text.MessageFormat;
029    import java.text.SimpleDateFormat;
030    import java.util.ArrayList;
031    import java.util.Collection;
032    import java.util.HashMap;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    
037    import org.apache.commons.lang.StringUtils;
038    import org.kuali.kfs.coa.businessobject.A21SubAccount;
039    import org.kuali.kfs.coa.businessobject.Account;
040    import org.kuali.kfs.coa.businessobject.AccountingPeriod;
041    import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRate;
042    import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRateDetail;
043    import org.kuali.kfs.coa.businessobject.ObjectCode;
044    import org.kuali.kfs.coa.businessobject.OffsetDefinition;
045    import org.kuali.kfs.coa.businessobject.SubAccount;
046    import org.kuali.kfs.coa.dataaccess.IndirectCostRecoveryRateDetailDao;
047    import org.kuali.kfs.coa.service.AccountingPeriodService;
048    import org.kuali.kfs.coa.service.ObjectCodeService;
049    import org.kuali.kfs.coa.service.OffsetDefinitionService;
050    import org.kuali.kfs.coa.service.SubAccountService;
051    import org.kuali.kfs.gl.GeneralLedgerConstants;
052    import org.kuali.kfs.gl.batch.PosterIndirectCostRecoveryEntriesStep;
053    import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
054    import org.kuali.kfs.gl.batch.service.PostTransaction;
055    import org.kuali.kfs.gl.batch.service.PosterService;
056    import org.kuali.kfs.gl.batch.service.RunDateService;
057    import org.kuali.kfs.gl.batch.service.VerifyTransaction;
058    import org.kuali.kfs.gl.businessobject.ExpenditureTransaction;
059    import org.kuali.kfs.gl.businessobject.OriginEntryFull;
060    import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
061    import org.kuali.kfs.gl.businessobject.Reversal;
062    import org.kuali.kfs.gl.businessobject.Transaction;
063    import org.kuali.kfs.gl.dataaccess.ExpenditureTransactionDao;
064    import org.kuali.kfs.gl.dataaccess.ReversalDao;
065    import org.kuali.kfs.gl.report.LedgerSummaryReport;
066    import org.kuali.kfs.gl.report.TransactionListingReport;
067    import org.kuali.kfs.gl.service.OriginEntryGroupService;
068    import org.kuali.kfs.gl.service.OriginEntryService;
069    import org.kuali.kfs.sys.KFSConstants;
070    import org.kuali.kfs.sys.KFSKeyConstants;
071    import org.kuali.kfs.sys.KFSPropertyConstants;
072    import org.kuali.kfs.sys.Message;
073    import org.kuali.kfs.sys.businessobject.SystemOptions;
074    import org.kuali.kfs.sys.businessobject.UniversityDate;
075    import org.kuali.kfs.sys.context.SpringContext;
076    import org.kuali.kfs.sys.dataaccess.UniversityDateDao;
077    import org.kuali.kfs.sys.exception.InvalidFlexibleOffsetException;
078    import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
079    import org.kuali.kfs.sys.service.ReportWriterService;
080    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
081    import org.kuali.rice.kns.service.BusinessObjectService;
082    import org.kuali.rice.kns.service.DataDictionaryService;
083    import org.kuali.rice.kns.service.DateTimeService;
084    import org.kuali.rice.kns.service.KualiConfigurationService;
085    import org.kuali.rice.kns.service.ParameterService;
086    import org.kuali.rice.kns.service.PersistenceService;
087    import org.kuali.rice.kns.service.PersistenceStructureService;
088    import org.kuali.rice.kns.util.KualiDecimal;
089    import org.kuali.rice.kns.util.ObjectUtils;
090    import org.springframework.transaction.annotation.Transactional;
091    
092    /**
093     * The base implementation of PosterService
094     */
095    @Transactional
096    public class PosterServiceImpl implements PosterService {
097        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PosterServiceImpl.class);
098    
099        public static final KualiDecimal WARNING_MAX_DIFFERENCE = new KualiDecimal("0.03");
100        public static final String DATE_FORMAT_STRING = "yyyyMMdd";
101    
102        private List transactionPosters;
103        private VerifyTransaction verifyTransaction;
104        private OriginEntryService originEntryService;
105        private OriginEntryGroupService originEntryGroupService;
106        private DateTimeService dateTimeService;
107        private ReversalDao reversalDao;
108        private UniversityDateDao universityDateDao;
109        private AccountingPeriodService accountingPeriodService;
110        private ExpenditureTransactionDao expenditureTransactionDao;
111        private IndirectCostRecoveryRateDetailDao indirectCostRecoveryRateDetailDao;
112        private ObjectCodeService objectCodeService;
113        private ParameterService parameterService;
114        private KualiConfigurationService configurationService;
115        private FlexibleOffsetAccountService flexibleOffsetAccountService;
116        private RunDateService runDateService;
117        private SubAccountService subAccountService;
118        private OffsetDefinitionService offsetDefinitionService;
119        private DataDictionaryService dataDictionaryService;
120        private BusinessObjectService businessObjectService;
121        private PersistenceStructureService persistenceStructureService;
122        private ReportWriterService reportWriterService;
123        private ReportWriterService errorListingReportWriterService;
124        private ReportWriterService reversalReportWriterService;
125        private ReportWriterService ledgerSummaryReportWriterService;
126        
127        //private File OUTPUT_ERR_FILE;
128        //private PrintStream OUTPUT_ERR_FILE_ps;
129        //private PrintStream OUTPUT_GLE_FILE_ps;
130        private String batchFileDirectoryName;
131        //private BufferedReader INPUT_GLE_FILE_br = null;
132        //private FileReader INPUT_GLE_FILE = null;
133        private AccountingCycleCachingService accountingCycleCachingService;
134    
135        /**
136         * Post scrubbed GL entries to GL tables.
137         */
138        public void postMainEntries() {
139            LOG.debug("postMainEntries() started");
140            Date runDate = dateTimeService.getCurrentSqlDate();
141            try{
142                FileReader INPUT_GLE_FILE = new FileReader(batchFileDirectoryName + File.separator +  GeneralLedgerConstants.BatchFileSystem.POSTER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
143                File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);    
144                
145                postEntries(PosterService.MODE_ENTRIES, INPUT_GLE_FILE, null, OUTPUT_ERR_FILE);
146                
147                INPUT_GLE_FILE.close();
148            } catch (FileNotFoundException e1) {
149                e1.printStackTrace();
150                throw new RuntimeException("PosterMainEntries Stopped: " + e1.getMessage(), e1);
151            } catch (IOException ioe) {
152                LOG.error("postMainEntries stopped due to: " + ioe.getMessage(), ioe);
153                throw new RuntimeException(ioe);
154            }
155        }
156    
157        /**
158         * Post reversal GL entries to GL tables.
159         */
160        public void postReversalEntries() {
161            LOG.debug("postReversalEntries() started");
162            Date runDate = dateTimeService.getCurrentSqlDate();
163            try{
164                PrintStream OUTPUT_GLE_FILE_ps = new PrintStream(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.REVERSAL_POSTER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
165                File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.REVERSAL_POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
166                
167                postEntries(PosterService.MODE_REVERSAL, null, OUTPUT_GLE_FILE_ps, OUTPUT_ERR_FILE);
168                
169                OUTPUT_GLE_FILE_ps.close();
170            } catch (FileNotFoundException e1) {
171                e1.printStackTrace();
172                throw new RuntimeException("PosterReversalEntries Stopped: " + e1.getMessage(), e1);
173            }
174        }
175    
176        /**
177         * Post ICR GL entries to GL tables.
178         */
179        public void postIcrEntries() {
180            LOG.debug("postIcrEntries() started");
181            Date runDate = dateTimeService.getCurrentSqlDate();
182            try{
183                FileReader INPUT_GLE_FILE = new FileReader(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_POSTER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
184                File OUTPUT_ERR_FILE = new File(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_POSTER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
185            
186                postEntries(PosterService.MODE_ICR, INPUT_GLE_FILE, null, OUTPUT_ERR_FILE);
187                
188                INPUT_GLE_FILE.close();
189            } catch (FileNotFoundException e1) {
190                e1.printStackTrace();
191                throw new RuntimeException("PosterIcrEntries Stopped: " + e1.getMessage(), e1);
192            } catch (IOException ioe) {
193                LOG.error("postIcrEntries stopped due to: " + ioe.getMessage(), ioe);
194                throw new RuntimeException(ioe);
195            }
196        }
197    
198        /**
199         * Actually post the entries. The mode variable decides which entries to post.
200         * 
201         * @param mode the poster's current run mode
202         */
203        protected void postEntries(int mode, FileReader INPUT_GLE_FILE, PrintStream OUTPUT_GLE_FILE_ps, File OUTPUT_ERR_FILE) throws FileNotFoundException {
204            if (LOG.isDebugEnabled()) {
205                LOG.debug("postEntries() started");
206            }
207            
208            PrintStream OUTPUT_ERR_FILE_ps = new PrintStream(OUTPUT_ERR_FILE);
209            BufferedReader INPUT_GLE_FILE_br = null;
210            if (INPUT_GLE_FILE != null) {
211                INPUT_GLE_FILE_br = new BufferedReader(INPUT_GLE_FILE);
212            }
213            
214            String GLEN_RECORD;
215            Date executionDate = new Date(dateTimeService.getCurrentDate().getTime());
216            Date runDate = new Date(runDateService.calculateRunDate(executionDate).getTime());
217            UniversityDate runUniversityDate = universityDateDao.getByPrimaryKey(runDate);
218            LedgerSummaryReport ledgerSummaryReport = new LedgerSummaryReport();
219    
220            // Build the summary map so all the possible combinations of destination & operation
221            // are included in the summary part of the report.
222            Map reportSummary = new HashMap();
223            for (Iterator posterIter = transactionPosters.iterator(); posterIter.hasNext();) {
224                PostTransaction poster = (PostTransaction) posterIter.next();
225                reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.DELETE_CODE, new Integer(0));
226                reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.INSERT_CODE, new Integer(0));
227                reportSummary.put(poster.getDestinationName() + "," + GeneralLedgerConstants.UPDATE_CODE, new Integer(0));
228            }
229            int ecount = 0;
230            try {
231                if ((mode == PosterService.MODE_ENTRIES) || (mode == PosterService.MODE_ICR)) {
232                    LOG.debug("postEntries() Processing groups");
233                    while ((GLEN_RECORD = INPUT_GLE_FILE_br.readLine()) != null) {
234                        if (!org.apache.commons.lang.StringUtils.isEmpty(GLEN_RECORD) && !org.apache.commons.lang.StringUtils.isBlank(GLEN_RECORD.trim())) {
235                            ecount++;
236    
237                            GLEN_RECORD = org.apache.commons.lang.StringUtils.rightPad(GLEN_RECORD, 183, ' ');
238                            OriginEntryFull tran = new OriginEntryFull();
239    
240                            // checking parsing process and stop poster when it has errors. 
241                            List<Message> parsingError = new ArrayList();
242                            parsingError = tran.setFromTextFileForBatch(GLEN_RECORD, ecount);
243                            if (parsingError.size() > 0) {
244                                String messages = "";
245                                for(Message msg : parsingError) {messages += msg + " ";}
246                                throw new RuntimeException("Exception happened from parsing process: " + messages);
247                            }
248                            // need to pass ecount for building better message
249                            addReporting(reportSummary, "SEQUENTIAL", GeneralLedgerConstants.SELECT_CODE);
250                            postTransaction(tran, mode, reportSummary, ledgerSummaryReport, OUTPUT_ERR_FILE_ps, runUniversityDate, GLEN_RECORD, OUTPUT_GLE_FILE_ps);
251                            
252                            if (ecount % 1000 == 0) {
253                                LOG.info("postEntries() Posted Entry " + ecount);
254                            }
255                        }
256                    }
257                    if (INPUT_GLE_FILE_br != null) {    
258                        INPUT_GLE_FILE_br.close();
259                    }
260                    OUTPUT_ERR_FILE_ps.close();
261                    reportWriterService.writeStatisticLine("SEQUENTIAL RECORDS READ                    %,9d", reportSummary.get("SEQUENTIAL,S"));
262                }
263                else {
264                    if (LOG.isDebugEnabled()) {
265                        LOG.debug("postEntries() Processing reversal transactions");
266                    }
267                    
268                    final String GL_REVERSAL_T = getPersistenceStructureService().getTableName(Reversal.class);
269                    Iterator reversalTransactions = reversalDao.getByDate(runDate);
270                    TransactionListingReport reversalListingReport = new TransactionListingReport();
271                    while (reversalTransactions.hasNext()) {
272                        ecount++;
273                        Transaction tran = (Transaction) reversalTransactions.next();
274                        addReporting(reportSummary, GL_REVERSAL_T, GeneralLedgerConstants.SELECT_CODE);
275    
276                        boolean posted = postTransaction(tran, mode, reportSummary, ledgerSummaryReport, OUTPUT_ERR_FILE_ps, runUniversityDate, GL_REVERSAL_T, OUTPUT_GLE_FILE_ps);
277                        
278                        if (posted) {
279                            reversalListingReport.generateReport(reversalReportWriterService, tran);
280                        }
281                        
282                        if (ecount % 1000 == 0) {
283                            LOG.info("postEntries() Posted Entry " + ecount);
284                        }
285                    }
286                    
287                    OUTPUT_ERR_FILE_ps.close();
288                    
289                    reportWriterService.writeStatisticLine("GLRV RECORDS READ (GL_REVERSAL_T)          %,9d", reportSummary.get("GL_REVERSAL_T,S"));
290                    reversalListingReport.generateStatistics(reversalReportWriterService);
291                }
292                
293                //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
294                reportWriterService.writeStatisticLine("GLEN RECORDS INSERTED (GL_ENTRY_T)         %,9d", reportSummary.get("GL_ENTRY_T,I"));
295                reportWriterService.writeStatisticLine("GLBL RECORDS INSERTED (GL_BALANCE_T)       %,9d", reportSummary.get("GL_BALANCE_T,I"));
296                reportWriterService.writeStatisticLine("GLBL RECORDS UPDATED  (GL_BALANCE_T)       %,9d", reportSummary.get("GL_BALANCE_T,U"));
297                reportWriterService.writeStatisticLine("GLEX RECORDS INSERTED (GL_EXPEND_TRN_T)    %,9d", reportSummary.get("GL_EXPEND_TRN_T,I"));
298                reportWriterService.writeStatisticLine("GLEX RECORDS UPDATED  (GL_EXPEND_TRN_T)    %,9d", reportSummary.get("GL_EXPEND_TRN_T,U"));
299                reportWriterService.writeStatisticLine("GLEC RECORDS INSERTED (GL_ENCUMBRANCE_T)   %,9d", reportSummary.get("GL_ENCUMBRANCE_T,I"));
300                reportWriterService.writeStatisticLine("GLEC RECORDS UPDATED  (GL_ENCUMBRANCE_T)   %,9d", reportSummary.get("GL_ENCUMBRANCE_T,U"));
301                reportWriterService.writeStatisticLine("GLRV RECORDS INSERTED (GL_REVERSAL_T)      %,9d", reportSummary.get("GL_REVERSAL_T,I"));
302                reportWriterService.writeStatisticLine("GLRV RECORDS DELETED  (GL_REVERSAL_T)      %,9d", reportSummary.get("GL_REVERSAL_T,D"));
303                reportWriterService.writeStatisticLine("SFBL RECORDS INSERTED (GL_SF_BALANCES_T)   %,9d", reportSummary.get("GL_SF_BALANCES_T,I"));
304                reportWriterService.writeStatisticLine("SFBL RECORDS UPDATED  (GL_SF_BALANCES_T)   %,9d", reportSummary.get("GL_SF_BALANCES_T,U"));
305                reportWriterService.writeStatisticLine("ACBL RECORDS INSERTED (GL_ACCT_BALANCES_T) %,9d", reportSummary.get("GL_ACCT_BALANCES_T,I"));
306                reportWriterService.writeStatisticLine("ACBL RECORDS UPDATED  (GL_ACCT_BALANCES_T) %,9d", reportSummary.get("GL_ACCT_BALANCES_T,U"));
307                reportWriterService.writeStatisticLine("ERROR RECORDS WRITTEN                      %,9d", reportSummary.get("WARNING,I"));
308            }
309            catch (RuntimeException re) {
310                LOG.error("postEntries stopped due to: " + re.getMessage() + " on line number : " + ecount, re);
311                throw new RuntimeException("PosterService Stopped: " + re.getMessage(), re);
312            }
313            catch (IOException e) {
314                LOG.error("postEntries stopped due to: " + e.getMessage(), e);
315                throw new RuntimeException(e);
316            }
317            catch (Exception e) {
318                // do nothing - handled in postTransaction method.
319            }
320    
321            LOG.info("postEntries() done, total count = " + ecount);
322            // Generate the reports
323            ledgerSummaryReport.writeReport(ledgerSummaryReportWriterService);
324            new TransactionListingReport().generateReport(errorListingReportWriterService, new OriginEntryFileIterator(OUTPUT_ERR_FILE));
325        }
326    
327        /**
328         * Runs the given transaction through each transaction posting algorithms associated with this instance
329         * 
330         * @param tran a transaction to post
331         * @param mode the mode the poster is running in
332         * @param reportSummary a Map of summary counts generated by the posting process
333         * @param ledgerSummaryReport for summary reporting
334         * @param invalidGroup the group to save invalid entries to
335         * @param runUniversityDate the university date of this poster run
336         * @param line
337         * @return whether the transaction was posted or not. Useful if calling class attempts to report on the transaction
338         */
339        protected boolean postTransaction(Transaction tran, int mode, Map<String,Integer> reportSummary, LedgerSummaryReport ledgerSummaryReport, PrintStream invalidGroup, UniversityDate runUniversityDate, String line, PrintStream OUTPUT_GLE_FILE_ps) {
340    
341            List<Message> errors = new ArrayList();
342            Transaction originalTransaction = tran;
343    
344            try {
345                final String GL_ORIGIN_ENTRY_T = getPersistenceStructureService().getTableName(OriginEntryFull.class);
346    
347                // Update select count in the report
348                if ((mode == PosterService.MODE_ENTRIES) || (mode == PosterService.MODE_ICR)) {
349                    addReporting(reportSummary, GL_ORIGIN_ENTRY_T, GeneralLedgerConstants.SELECT_CODE);
350                }
351                // If these are reversal entries, we need to reverse the entry and
352                // modify a few fields
353                if (mode == PosterService.MODE_REVERSAL) {
354                    Reversal reversal = new Reversal(tran);
355                    // Reverse the debit/credit code
356                    if (KFSConstants.GL_DEBIT_CODE.equals(reversal.getTransactionDebitCreditCode())) {
357                        reversal.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
358                    }
359                    else if (KFSConstants.GL_CREDIT_CODE.equals(reversal.getTransactionDebitCreditCode())) {
360                        reversal.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
361                    }
362                    UniversityDate udate = universityDateDao.getByPrimaryKey(reversal.getFinancialDocumentReversalDate());
363                    if (udate != null) {
364                        reversal.setUniversityFiscalYear(udate.getUniversityFiscalYear());
365                        reversal.setUniversityFiscalPeriodCode(udate.getUniversityFiscalAccountingPeriod());
366                        AccountingPeriod ap = accountingPeriodService.getByPeriod(reversal.getUniversityFiscalPeriodCode(), reversal.getUniversityFiscalYear());
367                        if (ap != null) {
368                            if (!ap.isActive()) { // Make sure accounting period is closed
369                                reversal.setUniversityFiscalYear(runUniversityDate.getUniversityFiscalYear());
370                                reversal.setUniversityFiscalPeriodCode(runUniversityDate.getUniversityFiscalAccountingPeriod());
371                            }
372                            reversal.setFinancialDocumentReversalDate(null);
373                            String newDescription = KFSConstants.GL_REVERSAL_DESCRIPTION_PREFIX + reversal.getTransactionLedgerEntryDescription();
374                            if (newDescription.length() > 40) {
375                                newDescription = newDescription.substring(0, 40);
376                            }
377                            reversal.setTransactionLedgerEntryDescription(newDescription);
378                        }
379                        else {
380                            errors.add(new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_UNIV_DATE_NOT_IN_ACCOUNTING_PERIOD_TABLE), Message.TYPE_WARNING));
381                        }
382                    }
383                    else {
384                        errors.add(new Message (configurationService.getPropertyString(KFSKeyConstants.ERROR_REVERSAL_DATE_NOT_IN_UNIV_DATE_TABLE) , Message.TYPE_WARNING));
385                    }
386                    // Make sure the row will be unique when adding to the entries table by adjusting the transaction sequence id
387                    int maxSequenceId = accountingCycleCachingService.getMaxSequenceNumber(reversal);
388                    reversal.setTransactionLedgerEntrySequenceNumber(new Integer(maxSequenceId + 1));
389    
390                    PersistenceService ps = SpringContext.getBean(PersistenceService.class);
391                    ps.retrieveNonKeyFields(reversal);
392                    tran = reversal;
393                }
394                else {
395                    tran.setChart(accountingCycleCachingService.getChart(tran.getChartOfAccountsCode()));
396                    tran.setAccount(accountingCycleCachingService.getAccount(tran.getChartOfAccountsCode(), tran.getAccountNumber()));
397                    tran.setObjectType(accountingCycleCachingService.getObjectType(tran.getFinancialObjectTypeCode()));
398                    tran.setBalanceType(accountingCycleCachingService.getBalanceType(tran.getFinancialBalanceTypeCode()));
399                    tran.setOption(accountingCycleCachingService.getSystemOptions(tran.getUniversityFiscalYear()));
400    
401                    ObjectCode objectCode = accountingCycleCachingService.getObjectCode(tran.getUniversityFiscalYear(), tran.getChartOfAccountsCode(), tran.getFinancialObjectCode());
402                    if (ObjectUtils.isNull(objectCode)) {
403                        LOG.warn(configurationService.getPropertyString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + tran.getUniversityFiscalYear() + "," + tran.getChartOfAccountsCode() + "," + tran.getFinancialObjectCode());                    
404                        errors.add(new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + tran.getUniversityFiscalYear() + "," + tran.getChartOfAccountsCode() + "," + tran.getFinancialObjectCode(), Message.TYPE_WARNING));                    
405                    }
406                    else {
407                        tran.setFinancialObject(accountingCycleCachingService.getObjectCode(tran.getUniversityFiscalYear(), tran.getChartOfAccountsCode(), tran.getFinancialObjectCode()));
408                    }
409    
410                    // Make sure the row will be unique when adding to the entries table by adjusting the transaction sequence id
411                    int maxSequenceId = accountingCycleCachingService.getMaxSequenceNumber(tran);
412                    ((OriginEntryFull) tran).setTransactionLedgerEntrySequenceNumber(new Integer(maxSequenceId + 1));
413                }
414    
415                if (errors.size() == 0) {
416                    try {
417                        errors = verifyTransaction.verifyTransaction(tran);
418                    }
419                    catch (Exception e) {
420                        errors.add(new Message(e.toString() + " occurred for this record.", Message.TYPE_FATAL));
421                    }
422                }
423    
424                if (errors.size() > 0) {
425                    // Error on this transaction
426                    reportWriterService.writeError(tran, errors);
427                    addReporting(reportSummary, "WARNING", GeneralLedgerConstants.INSERT_CODE);
428                    try {
429                        writeErrorEntry(line, invalidGroup);
430                    }
431                    catch (IOException ioe) {
432                        LOG.error("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
433                        throw new RuntimeException("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
434                    }
435                }
436                else {
437                    // No error so post it
438                    for (Iterator posterIter = transactionPosters.iterator(); posterIter.hasNext();) {
439                        PostTransaction poster = (PostTransaction) posterIter.next();
440                        String actionCode = poster.post(tran, mode, runUniversityDate.getUniversityDate(), reportWriterService);
441    
442                        if (actionCode.startsWith(GeneralLedgerConstants.ERROR_CODE)) {
443                            errors = new ArrayList<Message>();
444                            errors.add(new Message(actionCode, Message.TYPE_WARNING));
445                            reportWriterService.writeError(tran, errors);
446                        }
447                        else if (actionCode.indexOf(GeneralLedgerConstants.INSERT_CODE) >= 0) {
448                            addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.INSERT_CODE);
449                        }
450                        else if (actionCode.indexOf(GeneralLedgerConstants.UPDATE_CODE) >= 0) {
451                            addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.UPDATE_CODE);
452                        }
453                        else if (actionCode.indexOf(GeneralLedgerConstants.DELETE_CODE) >= 0) {
454                            addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.DELETE_CODE);
455                        }
456                        else if (actionCode.indexOf(GeneralLedgerConstants.SELECT_CODE) >= 0) {
457                            addReporting(reportSummary, poster.getDestinationName(), GeneralLedgerConstants.SELECT_CODE);
458                        }
459                    }
460                    if (errors.size() == 0) {
461                        // Delete the reversal entry
462                        if (mode == PosterService.MODE_REVERSAL) {
463                            createOutputEntry(tran, OUTPUT_GLE_FILE_ps);
464                            reversalDao.delete((Reversal) originalTransaction);
465                            addReporting(reportSummary, getPersistenceStructureService().getTableName(Reversal.class), GeneralLedgerConstants.DELETE_CODE);
466                        }
467                        
468                        ledgerSummaryReport.summarizeEntry(new OriginEntryFull(tran));
469                        return true;
470                    }
471                }
472                
473                return false;
474            }
475            catch (IOException ioe) {
476                LOG.error("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
477                throw new RuntimeException("PosterServiceImpl Stopped: " + ioe.getMessage(), ioe);
478    
479            }
480            catch (RuntimeException re) {
481                LOG.error("PosterServiceImpl Stopped: " + re.getMessage(), re);
482                throw new RuntimeException("PosterServiceImpl Stopped: " + re.getMessage(), re);
483            }
484        }
485    
486        /**
487         * This step reads the expenditure table and uses the data to generate Indirect Cost Recovery transactions.
488         */
489        public void generateIcrTransactions() {
490            LOG.debug("generateIcrTransactions() started");
491    
492            Date executionDate = dateTimeService.getCurrentSqlDate();
493            Date runDate = new Date(runDateService.calculateRunDate(executionDate).getTime());
494    
495            try {
496                PrintStream OUTPUT_GLE_FILE_ps = new PrintStream(batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.ICR_TRANSACTIONS_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
497                
498                int reportExpendTranRetrieved = 0;
499                int reportExpendTranDeleted = 0;
500                int reportExpendTranKept = 0;
501                int reportOriginEntryGenerated = 0;
502                Iterator expenditureTransactions;
503                
504                try {
505                    expenditureTransactions = expenditureTransactionDao.getAllExpenditureTransactions();
506                }
507                catch (RuntimeException re) {
508                    LOG.error("generateIcrTransactions Stopped: " + re.getMessage());
509                    throw new RuntimeException("generateIcrTransactions Stopped: " + re.getMessage(), re);
510                }
511    
512                while (expenditureTransactions.hasNext()) {
513                    ExpenditureTransaction et = new ExpenditureTransaction();
514                    try {
515                        et = (ExpenditureTransaction) expenditureTransactions.next();
516                        reportExpendTranRetrieved++;
517    
518                        KualiDecimal transactionAmount = et.getAccountObjectDirectCostAmount();
519                        KualiDecimal distributionAmount = KualiDecimal.ZERO;
520    
521                        if (shouldIgnoreExpenditureTransaction(et)) {
522                            // Delete expenditure record
523                            expenditureTransactionDao.delete(et);
524                            reportExpendTranDeleted++;
525                            continue;
526                        }
527                        
528                        IndirectCostRecoveryGenerationMetadata icrGenerationMetadata = retrieveSubAccountIndirectCostRecoveryMetadata(et);
529                        if (icrGenerationMetadata == null) {
530                            // ICR information was not set up properly for sub-account, default to using ICR information from the account
531                            icrGenerationMetadata = retrieveAccountIndirectCostRecoveryMetadata(et);
532                        }
533    
534                        Collection<IndirectCostRecoveryRateDetail> automatedEntries = indirectCostRecoveryRateDetailDao.getActiveRateDetailsByRate(et.getUniversityFiscalYear(), icrGenerationMetadata.getFinancialIcrSeriesIdentifier());
535                        int automatedEntriesCount = automatedEntries.size();
536              
537                        if (automatedEntriesCount > 0) {
538                            for (Iterator icrIter = automatedEntries.iterator(); icrIter.hasNext();) {
539                                IndirectCostRecoveryRateDetail icrEntry = (IndirectCostRecoveryRateDetail) icrIter.next();
540                                KualiDecimal generatedTransactionAmount = null;
541    
542                                if (!icrIter.hasNext()) {
543                                    generatedTransactionAmount = distributionAmount;
544    
545                                    // Log differences that are over WARNING_MAX_DIFFERENCE
546                                    if (getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct()).subtract(distributionAmount).abs().isGreaterThan(WARNING_MAX_DIFFERENCE)) {
547                                        List<Message> warnings = new ArrayList<Message>();
548                                        warnings.add(new Message("ADJUSTMENT GREATER THAN " + WARNING_MAX_DIFFERENCE, Message.TYPE_WARNING));
549                                        reportWriterService.writeError(et, warnings);
550                                    }
551                                }
552                                else if (icrEntry.getTransactionDebitIndicator().equals(KFSConstants.GL_DEBIT_CODE)) {
553                                    generatedTransactionAmount = getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct());
554                                    distributionAmount = distributionAmount.add(generatedTransactionAmount);
555                                }
556                                else if (icrEntry.getTransactionDebitIndicator().equals(KFSConstants.GL_CREDIT_CODE)) {
557                                    generatedTransactionAmount = getPercentage(transactionAmount, icrEntry.getAwardIndrCostRcvyRatePct());
558                                    distributionAmount = distributionAmount.subtract(generatedTransactionAmount);
559                                }
560                                else {
561                                    // Log if D / C code not found
562                                    List<Message> warnings = new ArrayList<Message>();
563                                    warnings.add(new Message("DEBIT OR CREDIT CODE NOT FOUND", Message.TYPE_FATAL));
564                                    reportWriterService.writeError(et, warnings);
565                                }
566                                generateTransactions(et, icrEntry, generatedTransactionAmount, runDate, OUTPUT_GLE_FILE_ps, icrGenerationMetadata);
567                                
568                                reportOriginEntryGenerated = reportOriginEntryGenerated + 2;
569                            }
570                        }
571                        // Delete expenditure record
572                        expenditureTransactionDao.delete(et);
573                        reportExpendTranDeleted++;
574    
575                    }
576                    catch (RuntimeException re) {
577                        LOG.error("generateIcrTransactions Stopped: " + re.getMessage());
578                        throw new RuntimeException("generateIcrTransactions Stopped: " + re.getMessage(), re);
579                    }
580                    catch (Exception e) {
581                        List errorList = new ArrayList();
582                        errorList.add(new Message(e.toString() + " occurred for this record.", Message.TYPE_FATAL));
583                        reportWriterService.writeError(et, errorList);
584                    }
585                }
586                OUTPUT_GLE_FILE_ps.close();
587                reportWriterService.writeStatisticLine("GLEX RECORDS READ               (GL_EXPEND_TRN_T) %,9d", reportExpendTranRetrieved);
588                reportWriterService.writeStatisticLine("GLEX RECORDS DELETED            (GL_EXPEND_TRN_T) %,9d", reportExpendTranDeleted);
589                reportWriterService.writeStatisticLine("GLEX RECORDS KEPT DUE TO ERRORS (GL_EXPEND_TRN_T) %,9d", reportExpendTranKept);
590                reportWriterService.writeStatisticLine("TRANSACTIONS GENERATED                            %,9d", reportOriginEntryGenerated);
591            }
592            catch (FileNotFoundException e) {
593                throw new RuntimeException("generateIcrTransactions Stopped: " + e.getMessage(), e);
594            }
595        }
596    
597        /**
598         * Generate a transfer transaction and an offset transaction
599         * 
600         * @param et an expenditure transaction
601         * @param icrEntry the indirect cost recovery entry
602         * @param generatedTransactionAmount the amount of the transaction
603         * @param runDate the transaction date for the newly created origin entry
604         * @param group the group to save the origin entry to
605         */
606        protected void generateTransactions(ExpenditureTransaction et, IndirectCostRecoveryRateDetail icrRateDetail, KualiDecimal generatedTransactionAmount, Date runDate, PrintStream group, IndirectCostRecoveryGenerationMetadata icrGenerationMetadata) {
607    
608            BigDecimal pct = new BigDecimal(icrRateDetail.getAwardIndrCostRcvyRatePct().toString());
609            pct = pct.divide(BDONEHUNDRED);
610    
611            OriginEntryFull e = new OriginEntryFull();
612            e.setTransactionLedgerEntrySequenceNumber(0);
613    
614            // SYMBOL_USE_EXPENDITURE_ENTRY means we use the field from the expenditure entry, SYMBOL_USE_IRC_FROM_ACCOUNT
615            // means we use the ICR field from the account record, otherwise, use the field in the icrRateDetail
616            if (GeneralLedgerConstants.PosterService.SYMBOL_USE_EXPENDITURE_ENTRY.equals(icrRateDetail.getFinancialObjectCode()) || GeneralLedgerConstants.PosterService.SYMBOL_USE_ICR_FROM_ACCOUNT.equals(icrRateDetail.getFinancialObjectCode())) {
617                e.setFinancialObjectCode(et.getObjectCode());
618                e.setFinancialSubObjectCode(et.getSubObjectCode());
619            }
620            else {
621                e.setFinancialObjectCode(icrRateDetail.getFinancialObjectCode());
622                    e.setFinancialSubObjectCode(icrRateDetail.getFinancialSubObjectCode());
623                }
624    
625            if (GeneralLedgerConstants.PosterService.SYMBOL_USE_EXPENDITURE_ENTRY.equals(icrRateDetail.getAccountNumber())) {
626                e.setAccountNumber(et.getAccountNumber());
627                e.setChartOfAccountsCode(et.getChartOfAccountsCode());
628                e.setSubAccountNumber(et.getSubAccountNumber());
629            }
630            else if (GeneralLedgerConstants.PosterService.SYMBOL_USE_ICR_FROM_ACCOUNT.equals(icrRateDetail.getAccountNumber())) {
631                e.setAccountNumber(icrGenerationMetadata.getIndirectCostRecoveryAcctNbr());
632                e.setChartOfAccountsCode(icrGenerationMetadata.getIndirectCostRcvyFinCoaCode());
633                e.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
634            }
635            else {
636                e.setAccountNumber(icrRateDetail.getAccountNumber());
637                e.setSubAccountNumber(icrRateDetail.getSubAccountNumber());
638                e.setChartOfAccountsCode(icrRateDetail.getChartOfAccountsCode());
639                // TODO Reporting thing line 1946
640            }
641            // take care of infinite recursive error case - do not generate entries
642            if  ((et.getAccountNumber().equals(e.getAccountNumber() )) &&
643                    ( et.getChartOfAccountsCode().equals(e.getChartOfAccountsCode())) &&
644                    (et.getSubAccountNumber().equals(e.getSubAccountNumber())) &&
645                    (et.getObjectCode().equals(e.getFinancialObjectCode())) &&
646                    (et.getSubObjectCode().equals(e.getFinancialSubObjectCode()))) {
647                List<Message> warnings = new ArrayList<Message>();
648                warnings.add(new Message("Infinite recursive encumbrance error " +  et.getChartOfAccountsCode() + " " + et.getAccountNumber() + " " + et.getSubAccountNumber() + " " + et.getObjectCode() + " " + et.getSubObjectCode(), Message.TYPE_WARNING));
649                reportWriterService.writeError(et, warnings);
650                return;
651            } 
652    
653            e.setFinancialDocumentTypeCode(parameterService.getParameterValue(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY));
654            e.setFinancialSystemOriginationCode(parameterService.getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ORIGINATION_CODE));
655            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_STRING);
656            e.setDocumentNumber(sdf.format(runDate));
657            if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
658                e.setTransactionLedgerEntryDescription(getChargeDescription(pct, et.getObjectCode(), icrGenerationMetadata.getIndirectCostRecoveryTypeCode(), et.getAccountObjectDirectCostAmount().abs()));
659            }
660            else {
661                e.setTransactionLedgerEntryDescription(getOffsetDescription(pct, et.getAccountObjectDirectCostAmount().abs(), et.getChartOfAccountsCode(), et.getAccountNumber()));
662            }
663            e.setTransactionDate(new java.sql.Date(runDate.getTime()));
664            e.setTransactionDebitCreditCode(icrRateDetail.getTransactionDebitIndicator());
665            e.setFinancialBalanceTypeCode(et.getBalanceTypeCode());
666            e.setUniversityFiscalYear(et.getUniversityFiscalYear());
667            e.setUniversityFiscalPeriodCode(et.getUniversityFiscalAccountingPeriod());
668    
669            ObjectCode oc = objectCodeService.getByPrimaryId(e.getUniversityFiscalYear(), e.getChartOfAccountsCode(), e.getFinancialObjectCode());
670            if (oc == null) {
671                LOG.warn(configurationService.getPropertyString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND_FOR) + e.getUniversityFiscalYear() + "," + e.getChartOfAccountsCode() + "," + e.getFinancialObjectCode());
672                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
673            } else {
674                e.setFinancialObjectTypeCode(oc.getFinancialObjectTypeCode());
675            }
676    
677            if (generatedTransactionAmount.isNegative()) {
678                if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
679                    e.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
680                }
681                else {
682                    e.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
683                }
684                e.setTransactionLedgerEntryAmount(generatedTransactionAmount.negated());
685            }
686            else {
687                e.setTransactionLedgerEntryAmount(generatedTransactionAmount);
688            }
689    
690            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())) {
691                e.setDocumentNumber(parameterService.getParameterValue(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY));
692            }
693            e.setProjectCode(et.getProjectCode());
694            if (GeneralLedgerConstants.getDashOrganizationReferenceId().equals(et.getOrganizationReferenceId())) {
695                e.setOrganizationReferenceId(null);
696            }
697            else {
698                e.setOrganizationReferenceId(et.getOrganizationReferenceId());
699            }
700            // TODO 2031-2039
701            try {
702                createOutputEntry(e, group);
703            }
704            catch (IOException ioe) {
705                LOG.error("generateTransactions Stopped: " + ioe.getMessage());
706                throw new RuntimeException("generateTransactions Stopped: " + ioe.getMessage(), ioe);
707            }
708    
709            // Now generate Offset
710            e = new OriginEntryFull(e);
711            if (KFSConstants.GL_DEBIT_CODE.equals(e.getTransactionDebitCreditCode())) {
712                e.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
713            }
714            else {
715                e.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
716            }
717            e.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
718    
719            String offsetBalanceSheetObjectCodeNumber = determineIcrOffsetBalanceSheetObjectCodeNumber(e, et, icrRateDetail);
720            e.setFinancialObjectCode(offsetBalanceSheetObjectCodeNumber);
721            ObjectCode balSheetObjectCode = objectCodeService.getByPrimaryId(icrRateDetail.getUniversityFiscalYear(), e.getChartOfAccountsCode(), offsetBalanceSheetObjectCodeNumber);
722            if (balSheetObjectCode == null) {
723                List<Message> warnings = new ArrayList<Message>();
724                warnings.add(new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_INVALID_OFFSET_OBJECT_CODE) + icrRateDetail.getUniversityFiscalYear() + "-" + e.getChartOfAccountsCode() + "-" +offsetBalanceSheetObjectCodeNumber, Message.TYPE_WARNING));
725                reportWriterService.writeError(et, warnings);
726                
727            }
728            else {
729                e.setFinancialObjectTypeCode(balSheetObjectCode.getFinancialObjectTypeCode());
730            }
731    
732            if (KFSConstants.GL_DEBIT_CODE.equals(icrRateDetail.getTransactionDebitIndicator())) {
733                e.setTransactionLedgerEntryDescription(getChargeDescription(pct, et.getObjectCode(), icrGenerationMetadata.getIndirectCostRecoveryTypeCode(), et.getAccountObjectDirectCostAmount().abs()));
734            }
735            else {
736                e.setTransactionLedgerEntryDescription(getOffsetDescription(pct, et.getAccountObjectDirectCostAmount().abs(), et.getChartOfAccountsCode(), et.getAccountNumber()));
737            }
738    
739            try {
740                flexibleOffsetAccountService.updateOffset(e);
741            }
742            catch (InvalidFlexibleOffsetException ex) {
743                List<Message> warnings = new ArrayList<Message>();
744                warnings.add(new Message("FAILED TO GENERATE FLEXIBLE OFFSETS " + ex.getMessage(), Message.TYPE_WARNING));
745                reportWriterService.writeError(et, warnings);
746                LOG.warn("FAILED TO GENERATE FLEXIBLE OFFSETS FOR EXPENDITURE TRANSACTION " + et.toString(), ex);
747            }
748    
749            try {
750                createOutputEntry(e, group);
751            }
752            catch (IOException ioe) {
753                LOG.error("generateTransactions Stopped: " + ioe.getMessage());
754                throw new RuntimeException("generateTransactions Stopped: " + ioe.getMessage(), ioe);
755            }
756        }
757    
758        public final static KualiDecimal ONEHUNDRED = new KualiDecimal("100");
759        public final static DecimalFormat DFPCT = new DecimalFormat("#0.000");
760        public final static DecimalFormat DFAMT = new DecimalFormat("##########.00");
761        public final static BigDecimal BDONEHUNDRED = new BigDecimal("100");
762    
763        /**
764         * Returns ICR Generation Metadata based on SubAccount information if the SubAccount on the expenditure transaction is properly
765         * set up for ICR
766         * 
767         * @param et
768         * @param reportErrors
769         * @return null if the ET does not have a SubAccount properly set up for ICR
770         */
771        protected IndirectCostRecoveryGenerationMetadata retrieveSubAccountIndirectCostRecoveryMetadata(ExpenditureTransaction et) {
772            SubAccount subAccount = accountingCycleCachingService.getSubAccount(et.getChartOfAccountsCode(), et.getAccountNumber(), et.getSubAccountNumber());
773            if (ObjectUtils.isNotNull(subAccount)) {
774                subAccount.setA21SubAccount(accountingCycleCachingService.getA21SubAccount(et.getChartOfAccountsCode(), et.getAccountNumber(), et.getSubAccountNumber()));
775            }
776    
777            if (ObjectUtils.isNotNull(subAccount) && ObjectUtils.isNotNull(subAccount.getA21SubAccount())) {
778                A21SubAccount a21SubAccount = subAccount.getA21SubAccount();
779                if (StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryTypeCode()) && StringUtils.isBlank(a21SubAccount.getFinancialIcrSeriesIdentifier()) && StringUtils.isBlank(a21SubAccount.getIndirectCostRcvyFinCoaCode()) && StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryAcctNbr())) {
780                    // all ICR fields were blank, therefore, this sub account was not set up for ICR
781                    return null;
782                }
783                // refresh the indirect cost recovery account, accounting cycle style!
784                if (!StringUtils.isBlank(a21SubAccount.getChartOfAccountsCode()) && !StringUtils.isBlank(a21SubAccount.getAccountNumber())) {
785                    a21SubAccount.setIndirectCostRecoveryAcct(accountingCycleCachingService.getAccount(a21SubAccount.getChartOfAccountsCode(), a21SubAccount.getAccountNumber()));
786                }
787    
788                // these fields will be used to construct warning messages
789                String warningMessagePattern = configurationService.getPropertyString(KFSKeyConstants.WARNING_ICR_GENERATION_PROBLEM_WITH_A21SUBACCOUNT_FIELD_BLANK_INVALID);
790                String subAccountBOLabel = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(SubAccount.class.getName()).getObjectLabel();
791                String subAccountValue = subAccount.getChartOfAccountsCode() + "-" + subAccount.getAccountNumber() + "-" + subAccount.getSubAccountNumber();
792                String accountBOLabel = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Account.class.getName()).getObjectLabel();
793                String accountValue = et.getChartOfAccountsCode() + "-" + et.getAccountNumber();
794    
795                boolean subAccountOK = true;
796    
797                // there were some ICR fields that were filled in, make sure they're all filled in and are valid values
798                a21SubAccount.setIndirectCostRecoveryType(accountingCycleCachingService.getIndirectCostRecoveryType(a21SubAccount.getIndirectCostRecoveryTypeCode()));
799                if (StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryTypeCode()) || ObjectUtils.isNull(a21SubAccount.getIndirectCostRecoveryType())) {
800                    String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.INDIRECT_COST_RECOVERY_TYPE_CODE);
801                    String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
802                    reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
803                    subAccountOK = false;
804                }
805    
806                if (StringUtils.isBlank(a21SubAccount.getFinancialIcrSeriesIdentifier())) {
807                    Map<String, Object> icrRatePkMap = new HashMap<String, Object>();
808                    icrRatePkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, et.getUniversityFiscalYear());
809                    icrRatePkMap.put(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, a21SubAccount.getFinancialIcrSeriesIdentifier());
810                    IndirectCostRecoveryRate indirectCostRecoveryRate = (IndirectCostRecoveryRate) businessObjectService.findByPrimaryKey(IndirectCostRecoveryRate.class, icrRatePkMap);
811                    if (indirectCostRecoveryRate == null) {
812                        String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER);
813                        String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
814                        reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
815                        subAccountOK = false;
816                    }
817                }
818    
819                if (StringUtils.isBlank(a21SubAccount.getIndirectCostRcvyFinCoaCode()) || StringUtils.isBlank(a21SubAccount.getIndirectCostRecoveryAcctNbr()) || ObjectUtils.isNull(a21SubAccount.getIndirectCostRecoveryAcct())) {
820                    String errorFieldName = dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.INDIRECT_COST_RECOVERY_CHART_OF_ACCOUNTS_CODE) + "/" + dataDictionaryService.getAttributeShortLabel(A21SubAccount.class, KFSPropertyConstants.INDIRECT_COST_RECOVERY_ACCOUNT_NUMBER);
821                    String warningMessage = MessageFormat.format(warningMessagePattern, errorFieldName, subAccountBOLabel, subAccountValue, accountBOLabel, accountValue);
822                    reportWriterService.writeError(et, new Message(warningMessage, Message.TYPE_WARNING));
823                    subAccountOK = false;
824                }
825    
826                if (subAccountOK) {
827                    IndirectCostRecoveryGenerationMetadata metadata = new IndirectCostRecoveryGenerationMetadata();
828                    metadata.setFinancialIcrSeriesIdentifier(a21SubAccount.getFinancialIcrSeriesIdentifier());
829                    metadata.setIndirectCostRecoveryTypeCode(a21SubAccount.getIndirectCostRecoveryTypeCode());
830                    metadata.setIndirectCostRcvyFinCoaCode(a21SubAccount.getIndirectCostRcvyFinCoaCode());
831                    metadata.setIndirectCostRecoveryAcctNbr(a21SubAccount.getIndirectCostRecoveryAcctNbr());
832                    metadata.setIndirectCostRecoveryAcct(a21SubAccount.getIndirectCostRecoveryAcct());
833                    return metadata;
834                }
835            }
836            return null;
837        }
838    
839    
840        protected IndirectCostRecoveryGenerationMetadata retrieveAccountIndirectCostRecoveryMetadata(ExpenditureTransaction et) {
841            Account account = et.getAccount();
842    
843            IndirectCostRecoveryGenerationMetadata metadata = new IndirectCostRecoveryGenerationMetadata();
844            
845            metadata.setFinancialIcrSeriesIdentifier(account.getFinancialIcrSeriesIdentifier());
846            metadata.setIndirectCostRecoveryTypeCode(account.getAcctIndirectCostRcvyTypeCd());
847            metadata.setIndirectCostRcvyFinCoaCode(account.getIndirectCostRcvyFinCoaCode());
848            metadata.setIndirectCostRecoveryAcctNbr(account.getIndirectCostRecoveryAcctNbr());
849            metadata.setIndirectCostRecoveryAcct(account.getIndirectCostRecoveryAcct());
850    
851            return metadata;
852        }
853    
854        /**
855         * Generates a percent of a KualiDecimal amount (great for finding out how much of an origin entry should be recouped by
856         * indirect cost recovery)
857         * 
858         * @param amount the original amount
859         * @param percent the percentage of that amount to calculate
860         * @return the percent of the amount
861         */
862        protected KualiDecimal getPercentage(KualiDecimal amount, BigDecimal percent) {
863            BigDecimal result = amount.bigDecimalValue().multiply(percent).divide(BDONEHUNDRED, 2, BigDecimal.ROUND_DOWN);
864            return new KualiDecimal(result);
865        }
866    
867        /**
868         * Generates the description of a charge
869         * 
870         * @param rate the ICR rate for this entry
871         * @param objectCode the object code of this entry
872         * @param type the ICR type code of this entry's account
873         * @param amount the amount of this entry
874         * @return a description for the charge entry
875         */
876        protected String getChargeDescription(BigDecimal rate, String objectCode, String type, KualiDecimal amount) {
877            BigDecimal newRate = rate.multiply(PosterServiceImpl.BDONEHUNDRED);
878    
879            StringBuffer desc = new StringBuffer("CHG ");
880            if (newRate.doubleValue() < 10) {
881                desc.append(" ");
882            }
883            desc.append(DFPCT.format(newRate));
884            desc.append("% ON ");
885            desc.append(objectCode);
886            desc.append(" (");
887            desc.append(type);
888            desc.append(")  ");
889            String amt = DFAMT.format(amount);
890            while (amt.length() < 13) {
891                amt = " " + amt;
892            }
893            desc.append(amt);
894            return desc.toString();
895        }
896    
897        /**
898         * Returns the description of a debit origin entry created by generateTransactions
899         * 
900         * @param rate the ICR rate that relates to this entry
901         * @param amount the amount of this entry
902         * @param chartOfAccountsCode the chart codce of the debit entry
903         * @param accountNumber the account number of the debit entry
904         * @return a description for the debit entry
905         */
906        protected String getOffsetDescription(BigDecimal rate, KualiDecimal amount, String chartOfAccountsCode, String accountNumber) {
907            BigDecimal newRate = rate.multiply(PosterServiceImpl.BDONEHUNDRED);
908    
909            StringBuffer desc = new StringBuffer("RCV ");
910            if (newRate.doubleValue() < 10) {
911                desc.append(" ");
912            }
913            desc.append(DFPCT.format(newRate));
914            desc.append("% ON ");
915            String amt = DFAMT.format(amount);
916            while (amt.length() < 13) {
917                amt = " " + amt;
918            }
919            desc.append(amt);
920            desc.append(" FRM ");
921            // desc.append(chartOfAccountsCode);
922            // desc.append("-");
923            desc.append(accountNumber);
924            return desc.toString();
925        }
926    
927        /**
928         * Increments a named count holding statistics about posted transactions
929         * 
930         * @param reporting a Map of counts generated by this process
931         * @param destination the destination of a given transaction
932         * @param operation the operation being performed on the transaction
933         */
934        protected void addReporting(Map reporting, String destination, String operation) {
935            String key = destination + "," + operation;
936            //TODO: remove this if block. Added to troubleshoot FSKD-194.
937            if("GL_EXPEND_TRN_T".equals(destination)){
938                LOG.info("Counting GLEX operation: "+operation);
939            }
940            if (reporting.containsKey(key)) {
941                Integer c = (Integer) reporting.get(key);
942                reporting.put(key, new Integer(c.intValue() + 1));
943            }
944            else {
945                reporting.put(key, new Integer(1));
946            }
947        }
948    
949        protected String determineIcrOffsetBalanceSheetObjectCodeNumber(OriginEntryInformation offsetEntry, ExpenditureTransaction et, IndirectCostRecoveryRateDetail icrRateDetail) {
950            String icrEntryDocumentType = parameterService.getParameterValue(PosterIndirectCostRecoveryEntriesStep.class, KFSConstants.SystemGroupParameterNames.GL_INDIRECT_COST_RECOVERY);
951            OffsetDefinition offsetDefinition = offsetDefinitionService.getByPrimaryId(offsetEntry.getUniversityFiscalYear(), offsetEntry.getChartOfAccountsCode(), icrEntryDocumentType, et.getBalanceTypeCode());
952            if (!ObjectUtils.isNull(offsetDefinition)) {
953                return offsetDefinition.getFinancialObjectCode();
954            } else {
955                return null;
956            }
957        }
958        
959        public void setVerifyTransaction(VerifyTransaction vt) {
960            verifyTransaction = vt;
961        }
962    
963        public void setTransactionPosters(List p) {
964            transactionPosters = p;
965        }
966    
967        public void setOriginEntryService(OriginEntryService oes) {
968            originEntryService = oes;
969        }
970    
971        public void setOriginEntryGroupService(OriginEntryGroupService oes) {
972            originEntryGroupService = oes;
973        }
974    
975        public void setDateTimeService(DateTimeService dts) {
976            dateTimeService = dts;
977        }
978    
979        public void setReversalDao(ReversalDao red) {
980            reversalDao = red;
981        }
982    
983        public void setUniversityDateDao(UniversityDateDao udd) {
984            universityDateDao = udd;
985        }
986    
987        public void setAccountingPeriodService(AccountingPeriodService aps) {
988            accountingPeriodService = aps;
989        }
990    
991        public void setExpenditureTransactionDao(ExpenditureTransactionDao etd) {
992            expenditureTransactionDao = etd;
993        }
994    
995        public void setIndirectCostRecoveryRateDetailDao(IndirectCostRecoveryRateDetailDao iaed) {
996            indirectCostRecoveryRateDetailDao = iaed;
997        }
998    
999        public void setObjectCodeService(ObjectCodeService ocs) {
1000            objectCodeService = ocs;
1001        }
1002    
1003        public void setConfigurationService(KualiConfigurationService configurationService) {
1004            this.configurationService = configurationService;
1005        }
1006    
1007        public void setParameterService(ParameterService parameterService) {
1008            this.parameterService = parameterService;
1009        }
1010    
1011        public void setFlexibleOffsetAccountService(FlexibleOffsetAccountService flexibleOffsetAccountService) {
1012            this.flexibleOffsetAccountService = flexibleOffsetAccountService;
1013        }
1014    
1015        public RunDateService getRunDateService() {
1016            return runDateService;
1017        }
1018    
1019        public void setRunDateService(RunDateService runDateService) {
1020            this.runDateService = runDateService;
1021        }
1022    
1023        protected void createOutputEntry(Transaction entry, PrintStream group) throws IOException {
1024            OriginEntryFull oef = new OriginEntryFull();
1025            oef.copyFieldsFromTransaction(entry);
1026            try {
1027                group.printf("%s\n", oef.getLine());
1028            }
1029            catch (Exception e) {
1030                throw new IOException(e.toString());
1031            }
1032        }
1033    
1034        protected void writeErrorEntry(String line, PrintStream invaliGroup) throws IOException {
1035            try {
1036                invaliGroup.printf("%s\n", line);
1037            } catch (Exception e) {
1038                throw new IOException(e.toString());
1039            }
1040        }
1041        
1042        public AccountingCycleCachingService getAccountingCycleCachingService() {
1043            return accountingCycleCachingService;
1044        }
1045    
1046        public void setAccountingCycleCachingService(AccountingCycleCachingService accountingCycleCachingService) {
1047            this.accountingCycleCachingService = accountingCycleCachingService;
1048        }
1049    
1050        public void setSubAccountService(SubAccountService subAccountService) {
1051            this.subAccountService = subAccountService;
1052        }
1053    
1054        public void setOffsetDefinitionService(OffsetDefinitionService offsetDefinitionService) {
1055            this.offsetDefinitionService = offsetDefinitionService;
1056        }
1057    
1058        protected DataDictionaryService getDataDictionaryService() {
1059            return dataDictionaryService;
1060        }
1061    
1062        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1063            this.dataDictionaryService = dataDictionaryService;
1064        }
1065    
1066        protected BusinessObjectService getBusinessObjectService() {
1067            return businessObjectService;
1068        }
1069    
1070        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1071            this.businessObjectService = businessObjectService;
1072        }
1073    
1074        protected boolean shouldIgnoreExpenditureTransaction(ExpenditureTransaction et) {
1075            if (ObjectUtils.isNotNull(et.getOption())) {
1076                SystemOptions options = et.getOption();
1077                return StringUtils.isNotBlank(options.getActualFinancialBalanceTypeCd()) && !options.getActualFinancialBalanceTypeCd().equals(et.getBalanceTypeCode());
1078            }
1079            return true;
1080        }
1081    
1082        public void setBatchFileDirectoryName(String batchFileDirectoryName) {
1083            this.batchFileDirectoryName = batchFileDirectoryName;
1084        }
1085    
1086        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
1087            this.persistenceStructureService = persistenceStructureService;
1088        }
1089    
1090        /**
1091         * Gets the persistenceStructureService attribute. 
1092         * @return Returns the persistenceStructureService.
1093         */
1094        public PersistenceStructureService getPersistenceStructureService() {
1095            return persistenceStructureService;
1096        }
1097    
1098        public void setReportWriterService(ReportWriterService reportWriterService) {
1099            this.reportWriterService = reportWriterService;
1100        }
1101    
1102        public void setErrorListingReportWriterService(ReportWriterService errorListingReportWriterService) {
1103            this.errorListingReportWriterService = errorListingReportWriterService;
1104        }
1105    
1106        public void setReversalReportWriterService(ReportWriterService reversalReportWriterService) {
1107            this.reversalReportWriterService = reversalReportWriterService;
1108        }
1109    
1110        public void setLedgerSummaryReportWriterService(ReportWriterService ledgerSummaryReportWriterService) {
1111            this.ledgerSummaryReportWriterService = ledgerSummaryReportWriterService;
1112        }
1113    }