001/* 002 * Copyright 2006-2009 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 017package org.kuali.ole.gl.batch.service.impl; 018 019import java.io.BufferedReader; 020import java.io.File; 021import java.io.FileNotFoundException; 022import java.io.FileReader; 023import java.io.IOException; 024import java.io.PrintStream; 025import java.math.BigDecimal; 026import java.sql.Date; 027import java.text.DecimalFormat; 028import java.text.MessageFormat; 029import java.text.SimpleDateFormat; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map; 036 037import org.apache.commons.lang.StringUtils; 038import org.kuali.ole.coa.businessobject.A21IndirectCostRecoveryAccount; 039import org.kuali.ole.coa.businessobject.A21SubAccount; 040import org.kuali.ole.coa.businessobject.Account; 041import org.kuali.ole.coa.businessobject.AccountingPeriod; 042import org.kuali.ole.coa.businessobject.IndirectCostRecoveryAccount; 043import org.kuali.ole.coa.businessobject.IndirectCostRecoveryRate; 044import org.kuali.ole.coa.businessobject.IndirectCostRecoveryRateDetail; 045import org.kuali.ole.coa.businessobject.ObjectCode; 046import org.kuali.ole.coa.businessobject.OffsetDefinition; 047import org.kuali.ole.coa.businessobject.SubAccount; 048import org.kuali.ole.coa.dataaccess.IndirectCostRecoveryRateDetailDao; 049import org.kuali.ole.coa.service.AccountingPeriodService; 050import org.kuali.ole.coa.service.ObjectCodeService; 051import org.kuali.ole.coa.service.OffsetDefinitionService; 052import org.kuali.ole.coa.service.SubAccountService; 053import org.kuali.ole.gl.GeneralLedgerConstants; 054import org.kuali.ole.gl.batch.PosterIndirectCostRecoveryEntriesStep; 055import org.kuali.ole.gl.batch.service.AccountingCycleCachingService; 056import org.kuali.ole.gl.batch.service.PostTransaction; 057import org.kuali.ole.gl.batch.service.PosterService; 058import org.kuali.ole.gl.batch.service.RunDateService; 059import org.kuali.ole.gl.batch.service.VerifyTransaction; 060import org.kuali.ole.gl.businessobject.ExpenditureTransaction; 061import org.kuali.ole.gl.businessobject.OriginEntryFull; 062import org.kuali.ole.gl.businessobject.OriginEntryInformation; 063import org.kuali.ole.gl.businessobject.Reversal; 064import org.kuali.ole.gl.businessobject.Transaction; 065import org.kuali.ole.gl.dataaccess.ExpenditureTransactionDao; 066import org.kuali.ole.gl.dataaccess.ReversalDao; 067import org.kuali.ole.gl.report.LedgerSummaryReport; 068import org.kuali.ole.gl.report.TransactionListingReport; 069import org.kuali.ole.gl.service.OriginEntryGroupService; 070import org.kuali.ole.gl.service.OriginEntryService; 071import org.kuali.ole.sys.OLEConstants; 072import org.kuali.ole.sys.OLEKeyConstants; 073import org.kuali.ole.sys.OLEPropertyConstants; 074import org.kuali.ole.sys.Message; 075import org.kuali.ole.sys.businessobject.SystemOptions; 076import org.kuali.ole.sys.businessobject.UniversityDate; 077import org.kuali.ole.sys.context.SpringContext; 078import org.kuali.ole.sys.dataaccess.UniversityDateDao; 079import org.kuali.ole.sys.exception.InvalidFlexibleOffsetException; 080import org.kuali.ole.sys.service.FlexibleOffsetAccountService; 081import org.kuali.ole.sys.service.ReportWriterService; 082import org.kuali.ole.sys.service.impl.OleParameterConstants; 083import org.kuali.rice.core.api.config.property.ConfigurationService; 084import org.kuali.rice.core.api.datetime.DateTimeService; 085import org.kuali.rice.core.api.util.type.KualiDecimal; 086import org.kuali.rice.coreservice.framework.parameter.ParameterService; 087import org.kuali.rice.kns.service.DataDictionaryService; 088import org.kuali.rice.krad.service.BusinessObjectService; 089import org.kuali.rice.krad.service.PersistenceService; 090import org.kuali.rice.krad.service.PersistenceStructureService; 091import org.kuali.rice.krad.util.ObjectUtils; 092import org.springframework.transaction.annotation.Transactional; 093 094/** 095 * The base implementation of PosterService 096 */ 097@Transactional 098public class PosterServiceImpl implements PosterService { 099 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}