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 }