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