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 }